Connect: Извлечение данных с помощью mapStateToProps
mapStateToProps
— первый аргумент connect
, используется для выбора той части данных из хранилища(store), которая нужна подключенному компоненту. Для краткости его часто называют просто mapState
.
- Вызывается при каждом изменении состояние хранилища(store).
- Получает всё состояние хранилища(store) и возвращает объект с необходимыми этому компоненту данными.
Определение `mapStateToProps'
mapStateToProps
должна быть определена как функция:
function mapStateToProps(state, ownProps?)
Она принимает первый аргумент, именующийся state
и необязательно второй аргумент own Props
. Функция должна возвращать простой объект, содержащий данные, необходимые подключенному компоненту.
Эта функция должна быть передана в качестве первого аргумента для connect
и будет вызываться при каждом изменении состояние Redux хранилища(store). Если вы не хотите подписываться на хранилище(store), передайте null
или undefined
в connect
вместо mapStateToProps
.
**Не имеет значения, написана ли функция mapStateToProps
с использованием ключевого слова function
(function map State(состояние) { }
) или в виде стрелочной функции (const mapState = (состояние) => { }
) — это будет работать одинаково в любом случае.
Аргументы
state
ownProps
(необязателен)
state
Первым аргументом функции mapStateToProps
является всё состояние хранилища Redux (то же значение, возвращаемое вызовом store.getState()
). Из-за этого первый аргумент традиционно называется просто state
. (Хотя вы можете присвоить аргументу любое имя, называть его store
было бы неправильно — это "значение состояния", а не "экземпляр хранилища(store)".)
Функция mapStateToProps
всегда должна объявляться по крайней мере с переданным state
.
// TodoList.js
function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}
export default connect(mapStateToProps)(TodoList)
ownProps
(необязательна)
Вы можете определить функцию mapStateToProps
со вторым аргументом ownProps
, если вашему компоненту нужны данные из его собственных пропсов для извлечения данных из хранилища(store). Этот аргумент будет содержать все пропсы, переданные компоненту-обертке, который сгенерировал connect
.
// Todo.js
function mapStateToProps(state, ownProps) {
const { visibilityFilter } = state
// ownProps будет выглядеть как { "id" : 123 }
const { id } = ownProps
const todo = getTodoById(state, id)
// компонент получает дополнительно:
return { todo, visibilityFilter }
}
// Позже, в вашем приложении родительский компонент отрисовывает:
;<ConnectedTodo id={123} />
// и ваш компонент получает props.id, props.todo и props.visibilityFilter
Вам не нужно включать значения из ownProps
в объект, возвращаемый из mapStateToProps
. connect
автоматически объединит разные источники пропсов в окончательный набор пропсов.
Возвращение
Ваша функция mapStateToProps
должна возвращать простой объект, содержащий данные, необходимые компоненту:
- Каждое поле в объекте станет пропсом для вашего компонента.
- Значения в полях будут использоваться при определении необходимости повторного рендеринга вашего компонента.
Например:
function mapStateToProps(state) {
return {
a: 42,
todos: state.todos,
filter: state.visibilityFilter,
}
}
// компонент получит: props.a, props.todos и props.filter
Примечание. В продвинутых сценариях, где требуется больше контроля над производительностью рендеринга,
mapStateToProps
также может возвращать функцию. В этом случае эта функция будет использоваться как окончательныйmapStateToProps
для конкретного экземпляра компонента. Это позволяет вам делать мемоизацию для каждого экземпляра. Смотри Расширенное использование: фабричные функции для дополнительных сведений, а также [PR #279](https://github.com/reduxjs/react-redux/ pull/279) и тесты, которые он добавляет. Большинству приложений это никогда не понадобится.
Руководство по использованию
Пусть mapStateToProps
изменяет данные из хранилища(store)
Функции mapStateToProps
могут и должны делать намного больше, чем просто возвращать state.someSlice
. Они несут ответственность за «изменение» данных хранилища в соответствии с потребностями этого компонента. Они могут возвращать значения в качестве определенного имени пропсов, объединять фрагменты данных из разных частей дерева состояний и преобразовывать данные хранилища (store) разными способами.
Использование функций Selector для извлечения и преобразования данных
Мы настоятельно рекомендуем использовать функции «Selector», чтобы помочь инкапсулировать процесс извлечения значений из определенных мест в дереве состояний. Функции мемоизированного селектора также играют ключевую роль в повышении производительности приложения (см. следующие разделы на этой странице и на странице Redux Продвинутое использование: вычисление полученных данных для получения более подробной информации о том, почему и как использовать селекторы.)
Функции mapStateToProps
должны быть быстрыми
Всякий раз, когда хранилище (store) изменяется, все функции mapStateToProps
всех подключенных компонентов будут исполняться. Из-за этого ваши функции mapStateToProps
должны работать как можно быстрее. Это также означает, что медленная функция mapStateToProps
может быть потенциальным узким местом вашего приложения.
В рамках идеи «изменения вида данных» функции mapStateToProps
часто должны преобразовывать данные различными способами (например, фильтровать массив, сопоставлять массив идентификаторов с соответствующими объектами или извлекать простые значения JS из Immutable.js объектов). Эти преобразования часто могут быть дорогостоящими как с точки зрения затрат на выполнение преобразования, так и с точки зрения повторного рендеринга компонента в результате. Если вас беспокоит производительность, убедитесь, что эти преобразования выполняются только при изменении входящих значений.
Функции mapStateToProps
должны быть чистыми и синхронными
Подобно Redux редюсеру(reducer), функция mapStateToProps
всегда должна быть на 100% чистой и синхронной. Она должна принимать только state
(и ownProps
) в качестве аргументов и возвращать необходимые компоненту данные в качестве свойств без изменения этих аргументов. Её не следует использовать для запуска асинхронного поведения, такого как вызовы AJAX для выборки данных, и она не должна быть объявлена как async
.
mapStateToProps
и производительность
Возвращаемые значения определяют, будет ли ваш компонент выполнять повторный рендеринг
React Redux внутренне реализует метод shouldComponentUpdate
, так что компонент-оболочка повторно отображает именно тогда, когда данные, необходимые вашему компоненту, изменились. По умолчанию React Redux решает, отличается ли содержимое объекта, возвращаемого из mapStateToProps
, используя ===
сравнение (сравнение без приведения типов) для каждого поля возвращаемого объекта. Если какое-либо из полей было изменено, ваш компонент будет повторно отрендерен, чтобы он мог получить обновленные значения в качестве пропсов. Обратите внимание, что возврат измененного объекта по той же ссылке является распространенной ошибкой, которая может привести к тому, что ваш компонент не будет повторно рендерится, как ожидалось.
Подытожим, поведение компонента, обернутого connect
с mapStateToProps
для извлечения данных из хранилища:
(state) => stateProps | (state, ownProps) => stateProps | |
---|---|---|
mapStateToProps запускается, когда: | state хранилища(store) изменился | state хранилища(store) изменился или любое поле ownProps отличается |
компонент заново рендерится, когда: | любое поле stateProps отличается | любое поле stateProps отличается или любое поле ownProps отличается |
Возвращайте новые ссылки на объекты только в случае необходимости
React Redux выполняет сравнение без приведения типов, чтобы увидеть, изменились ли результаты mapStateToProps
. Легко случайно вернуть новые ссылки на объекты или массивы, что может привести к повторному рендерингу вашего компонента, даже если данные на самом деле одни и те же.
Многие распространенные операции приводят к созданию новых ссылок на объекты или массивы:
- Создание новых массивов с помощью
someArray.map()
илиsomeArray.filter()
- Объединение массивов с помощью
array.concat
- Выбор части массива с помощью
array.slice
- Копирование значений с помощью
Object.assign
- Копирование значений с помощью оператора распространения
{ ...oldState, ...newData }
Поместите эти операции в мемоизированные функции селектора, чтобы убедиться, что они выполняются только в том случае, если входные значения изменились. Это также гарантирует, что если входные значения не изменились, mapStateToProps
по-прежнему будет возвращать те же значения результата, что и раньше, а connect
сможет пропустить повторный рендеринг.
Выполняйте дорогостоящие операции только при изменении данных
Преобразование данных часто может быть дорогостоящим (и обычно приводит к созданию новых ссылок на объекты). Чтобы ваша функция mapStateToProps
работала как можно быстрее, вам следует повторно запускать эти сложные преобразования только тогда, когда соответствующие данные изменились.
Есть несколько подходов для этого:
- Некоторые преобразования можно было выполнить в генераторе действий или редюсере(reducer), а обработанные данные можно было сохранить в хранилище.
- Преобразования также могут быть выполнены в методе
render()
компонента. - Если преобразование необходимо выполнить в функции
mapStateToProps
, мы рекомендуем использовать мемоизированные фукнции селектора, чтобы убедиться, что преобразование выполняется только при изменении входных значений.
Проблемы с производительностью Immutable.js
Автор Immutable.js Ли Байрон в Твиттере явно советует избегать toJS
, когда важна производительность:
Совет по улучшению производительности для #immutablejs: избегайте .toJS() .toObject() и .toArray() - всех медленных операций глубого копирования, которые делают structural sharing бесполезным.
Есть несколько других проблем с производительностью, которые следует учитывать при использовании Immutable.js — см. список ссылок в конце этой страницы для получения дополнительной информации.
Поведение и ошибки
mapStateToProps
не запустится, если состояние хранилища(store) такое же
Компонент-оболочка, сгенерированный connect
, подписывается на Redux хранилище(store). Каждый раз, когда действие отправляется, оно вызывает store.getState()
и проверяет, соответствует ли lastState === currentState
. Если два значения состояния идентичны по ссылке, то она не перезапустит вашу функцию mapStateToProps
, поскольку предполагает, что остальная часть состояния хранилища(store) также не изменилась.
Вспомогательная функция Redux combineReducers
пытается оптимизировать это. Если ни один из редюсеров(reducers) не вернул новое значение, тогда combineReducers
возвращает старый объект состояния вместо нового. Это означает, что мутация в редюсере(reducer) может привести к тому, что объект корневого состояния не будет обновляться, и поэтому пользовательский интерфейс не будет повторно рендерится.
Количество объявленных аргументов влияет на поведение
Только со (state)
функция запускается всякий раз, когда объект состояния корневого хранилища отличается. При (state, ownProps)
она запускается каждый раз, когда состояние хранилища(store) отличается, а ТАКЖЕ всякий раз, когда изменились реквизиты пропсов.
Это означает, что вы не должны добавлять аргумент ownProps
, если он вам действительно не нужен, иначе ваша функция mapStateToProps
будет запускаться чаще, чем нужно.
Есть несколько пограничных случаев, связанных с этим поведением. Количество обязательных аргументов определяет, будет ли mapStateToProps
получать ownProps
.
Если определение функции содержит один обязательный параметр, mapStateToProps
не получит ownProps
:
function mapStateToProps(state) {
console.log(state) // state
console.log(arguments[1]) // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
console.log(state) // state
console.log(ownProps) // {}
}
Он будет получать ownProps
, когда формальное определение функции содержит ноль или два обязательных параметра:
function mapStateToProps(state, ownProps) {
console.log(state) // state
console.log(ownProps) // ownProps
}
function mapStateToProps() {
console.log(arguments[0]) // state
console.log(arguments[1]) // ownProps
}
function mapStateToProps(...args) {
console.log(args[0]) // state
console.log(args[1]) // ownProps
}
Ссылки и источники
Руководства
- Redux на практике, Part 6: подключаемые списки, формы, and производительность
- Идеоматический Redux: использование библиотеки Reselect для инкапсуляции и производительности
Производительность
- Твит Ли Байрона предлагает избегать использования
toJS
,toArray
иtoObject
для повышения производительности - Улучшение производительности React и Redux с помощью Reselect
- Неизменяемые ссылки производительности данных
Вопросы и ответы