Вычисление производных данных¶
Reselect это простая библиотека для создания мемоизированных, пригодных для компоновки селекторных функций. Селекторы Reselect могут использоваться для эффективного вычисления производных данных из Redux store.
Причины использовать Мемоизированные Селекторы¶
Давайте вспомним наш Список Задач пример Todos List:
containers/VisibleTodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
В приведённом выше примере, mapStateToProps
вызывает getVisibleTodos
чтобы посчитать todos
. Это отлично работает, но есть недостаток: todos
расчитывается каждый раз, когда компонент обновляется. Если дерево состояний велико, или вычисление требует больших затрат, повторение вычисления при каждом обновлениии может привести к проблемам с производительностью. Reselect может помочь избежать этих излишних пересчётов.
Создание Мемоизированного Селектора¶
Мы хотели бы заменить getVisibleTodos
на мемоизированный селектор, который пересчитывает todos
когда значение state.todos
или state.visibilityFilter
изменяется, но не тогда когда изменения происходят в других (независимых) частях дерева состояний.
Reselect предоставляет функцию createSelector
для создания мемоизированных селекторов. В качестве аргументов createSelector
принимает массив входных селекторов и функцию преобразования. Если дерево состояний Redux мутируется таким образом, что послужит причиной изменения значения входного селектора, селектор вызовет свою функцию преобразования со значениями входных селекторов в качестве аргументов и вернёт результат. Если значения входных селекторов такие же как и в предыдущем вызове селектора, он вернёт ранее вычисленное значение, вместо того чтобы вызывать функцию преобразования.
Давайте определим мемоизированный селектор с именем getVisibleTodos
на замену не мемоизированной версии выше:
selectors/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
В примере выше, getVisibilityFilter
и getTodos
входные селекторы. Они создаются как обычные не мемоизированные селекторные функции, потому что они не преобразуют данные, которые они выбирают. Что же касается getVisibleTodos
- это мемоизированный селектор. Он принимает getVisibilityFilter
и getTodos
в качестве входных селекторов, и функцию преобразования, которая вычисляет отфильтрованный список задач (todos list).
Композиция Селекторов¶
Мемоизированный селектор сам по себе может быть входным селектором для другого мемоизированного селектора. Здесь getVisibleTodos
используется в качестве входного селектора для селектора, который затем фильтрует todos по ключевому слову:
1 2 3 4 5 6 7 8 9 |
|
Подключение Селектора к Redux Store¶
Если Вы используете React Redux, Вы можете вызывать селекторы в качестве регулярных функций внутри mapStateToProps()
:
containers/VisibleTodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Доступ к React Props в Селекторах¶
В этом разделе предоставлено гипотетическое расширение нашего приложения, которое позволяет ему поддерживать любое количество списков задач (Todo Lists). Пожалуйста, обратите внимание, полная реализация этого расширения требует изменений в редьюсерах (reducers), компонентах (components), экшенах (actions) и т.д., которые не имеют прямого отношения к обсуждаемым темам и для краткости были опущены.
До сих пор мы видели что селекторы получают состояние стора (store state) Redux в качестве аргумента, но селектор также может получать props.
Вот компонент App
который отображает три VisibleTodoList
компонента, каждый из которых имеет listId
prop:
components/App.js
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Каждый VisibleTodoList
контейнер должен выбирать различный срез состояния (state) в зависимости от значения listId
prop, поэтому давайте модифицируем getVisibilityFilter
и getTodos
для приёма аргумента props:
selectors/todoSelectors.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
props
может быть передан getVisibleTodos
из mapStateToProps
:
1 2 3 4 5 |
|
Итак, теперь getVisibleTodos
имеет доступ к props
, и всё кажется работает нормально.
Но есть проблема!
Использование селектора getVisibleTodos
с множественными вхождениями контейнера visibleTodoList
не будет правильно мемоизировано:
containers/VisibleTodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Селектор созданный с помощью createSelector
возвращает только кэшированное значение, когда его набор аргументов совпадает с его предыдущим набором аргументов. Если мы рендерим поочерёдно <VisibleTodoList listId="1" />
и <VisibleTodoList listId="2" />
, общий селектор будет поочерёдно принимать {listId: 1}
и {listId: 2}
как аргумент props
. Это приведёт к тому что аргументы будут разными для каждого вызова, поэтому селектор всегда будет пересчитывать, вместо того чтобы возвращать кэшированное значение. Мы увидим как преодолеть это ограничение в следующем разделе.
Совместное использование селекторов с несколькими компонентами¶
Примеры в этом разделе требуют React Redux v4.3.0 или выше
Чтобы совместно использовать селектор для нескольких компонентов VisibleTodoList
и сохранять мемоизацию, каждому экземпляру компонента нужна собственная личная копия селектора.
Давайте создадим функцию makeGetVisibleTodos
, которая возвращает новую копию селектора getVisibleTodos
при каждом вызове:
selectors/todoSelectors.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Нам также нужен способ предоставить каждому экземпляру контейнера доступ к его собственному селектору. Аргумент mapStateToProps
от connect
может помочь в этом.
Если аргумент mapStateToProps
предоставленный connect
возвращает функцию вместо объекта, он будет использоваться для создания отдельной функции mapStateToProps
для каждого экземпляра контейнера.
В приведённом ниже примере makeMapStateToProps
создаёт новый getVisibleTodos
селектор, и возвращает функцию mapStateToProps
, которая имеет эксклюзивный доступ к новому селектору:
1 2 3 4 5 6 7 8 9 |
|
Если мы передадим makeMapStateToProps
connect
, каждый экземпляр контейнера VisibleTodosList
получит свою собственную функцию mapStateToProps
с собственным селектором getVisibleTodos
. Мемоизация теперь будет работать правильно, независимо от порядка отрисовки (рендера) контейнеров VisibleTodoList
.
containers/VisibleTodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Следующие шаги¶
Ознакомьтесь с официальной документацией Reselect а также FAQ. Большинство проектов Redux начинают использовать Reselect когда у них возникают проблемы с производительностью из-за слишком большого количества вторичных вычислений и потерь в ре-рендеринге, поэтому убедитесь, что вы знакомы с ним, прежде чем создавать что-то большое. Также может быть полезно изучить его исходный код чтобы вы не думали что это волшебство.