Селекторы и библотека reselect

Селекторы – это функции, которые принимают state и достают (возвращают) из него данные, при этом могут при необходимости их обрабатывать дополнительно даже какой-то очень сложной логикой (фильтры, расчеты и т.д.). Эти функции-селекторы вызываются внутри mapStateToProps вместо обычного прямого доставания со стейта.

Reselect – это библиотека, благодаря которой селекторам не приходится постоянно перезапускать рендеринг без изменений в стейте. При первом вызове реселекта значения зависимостей (простых передаваемых внутрь селекторов) кешируются, а при следующих вызовах данные этих зависимостей сравниваются с кешироваными. Если данные простых селекторов не изменились, то сразу возвращается кеншированный результат и без лишней перерисовки компоненты, если же что-то из зависимостей изменилось в стейте по сравнению с кешем то тогда уже вызывается селектор с “дорогими” вычислениями.

Использование секлекторов

  1. Допустим у нас в mapStateToProps файла UsersContainer.jsx были следующие значения:
     let mapStateToProps = (state) => {
        
        return {
            users: state.profilePage.users,
            pageSize:state.profilePage.pageSize,
            totalUsersCount: state.profilePage.totalUsersCount,
            currentPage: state.profilePage.currentPage,
            isFetching: state.profilePage.isFetching(state),
            followingInProgress: state.profilePage.followingInProgress
        }
    }
  2. Чтобы применить селекторы мы создаем файл src/redux/users-selectors.js, в котором и прописываем селекторы для каждого из этих значений:
    export const getUsers = (state) => {
        return state.usersPage.users;
    }
    
    export const getPageSize = (state) => {
        return state.usersPage.pageSize;
    }
    
    export const getTotalUsersCount = (state) => {
        return state.usersPage.totalUsersCount;
    }
    
    export const getCurrentPage = (state) => {
        return state.usersPage.currentPage;
    }
    
    export const getIsFetching = (state) => {
        return state.usersPage.isFetching;
    }
    
    export const getFollowingInProgress = (state) => {
        return state.usersPage.followingInProgress;
    }

    Как видим, селекторы принято называть начиная с “get”. Все они экспортируются.

  3. Теперь все эти функции импортируем в наш файл UsersContainer.jsx и вызываем вместо прямого обращения к стейту внутриmapStateToProps:
    import { getPageSize, getUsers, getTotalUsersCount, getCurrentPage, getIsFetching, getFollowingInProgress } from '../../redux/users-selectors';
    
    ...
    
     let mapStateToProps = (state) => {
        
        return {
            users: getUsers(state),
            pageSize: getPageSize(state),
            totalUsersCount: getTotalUsersCount(state),
            currentPage: getCurrentPage(state),
            isFetching: getIsFetching(state),
            followingInProgress: getFollowingInProgress(state)
        }
    }
    
    ...

Использование библиотеки reselect

  1. Устанавливаем ее: npm install reselect либо yarn add reselect
  2. Внутри нашего файла users-selectors.js с селекторами импортируем createSelector:
    import { createSelector } from "reselect";
  3. Теперь в качестве примера применим reselect для getUsers. Для этого сам наш старый простой селектор getUsers, который только получал данные,  переименовываем в getUsersSelector, а затем создадим новый getUsers, который уже будет не просто селектор, а createSelector. При этом первым параметром он примет наш простой селектор getUsersSelector (называется зависимостью), а вторым – название переменной, которая будет содержать возвращаемое значение нашим простым селектором:
    import { createSelector } from "reselect";
    
    const getUsersSelector = (state) => {
        return state.usersPage.users;
    }
    
    export const getUsers = createSelector( getUsersSelector, (users) => {
        return users;
    })

    На самом деле это очень простой пример. На практике, например, одни селектор может вызываться еще одним промежуточным селектором, который делает какие-либо вычесления, а тот уже потом будет передан в криэйт-селектор:

    const getUsers = (state) => {
        return state.usersPage.users;
    }
    
    const getUsersSelector = (state) => {
       return getUsers(state).filter(u => true);
    }
    
    export const getUsersSuperSelector = createSelector( getUsersSelector, (users) => { 
       return users.filter(u => true); 
    })

    Либо же мы можем подписаться сразу на несколько селекторов (создать больше зависимостей) и производить более серьезные расчеты внутри, как селектора, так и криэйт-селектора:

    export const getUsers = createSelector( getUsersSelector, getIsFetching, (users) => { 
       return users.filter(u => true);
    })

     

Зачем нужны селекторы?

На первый взгляд может показаться, что они усложняют работу. Но благодаря им при каких-либо изменениях в названии переменных внутри стейта нам не нужно будет менять доставание со стейта в разных местах кода внутри mapStateToProps. Достаточно будет внести эти изменения внутри самой функции-селектора.

Зачем нужна библиотека reselect?

Селекторы сами по себе имеют несколько недостатков. Дело в том, что функция mapStateToProps запускается постоянно при любых изменениях в стейте, при этом перерисовка без применения селекторов (при доставании данных прямо со стейта) не происходит, если ничего не поменялось из тех объектов, на которые мы подписались внутри mapStateToProps. Так и должно быть. Но вот в случае использования селекторов перерисовка происходит постоянно при каждом запуске mapStateToProps,  даже если наши конкретные изначальные объекты в стейте не поменялись (точнее не создались их копии), ведь функция возвращает каждый раз новый объект.

В итоге постоянный рендеринг создает дополнительную нагрузку. Но даже это не самое страшное, особенно если селекторые простые и не производят каких-то очень сложных вычеслений. А хуже всего в этом то, что в таких условиях невозможно будет дебажить, так как дебагер будет постоянно срабатывать.

Поэтому библоитека reselect и применяется, чтобы создавать криэйт-селекторы, задавая в качестве первых параметров простой селектор (или несколько селекторов) и следя за изменением возвращаемых ими значений, сравнивая их с кэшированными. По сути происходит то же, что до этого нам делал mapStateToProps, только не с самим стейтом, а возвращаемыми селекторами значениями. В итоге reselect позволяет делать перерисовку и запускать более сложные расчеты только тогда, когда значение простого селектора изменилось. Подписываться внутри одного такого криэйт-селектора сразу можно на несколько селекторов, в результате чего перерисовка будет происходить при изменении в значения в любом из них.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *