Skip to main content

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 = (состояние) => { } ) — это будет работать одинаково в любом случае.

Аргументы

  1. state
  2. 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% чистой и синхронной. Она должна принимать только stateownProps) в качестве аргументов и возвращать необходимые компоненту данные в качестве свойств без изменения этих аргументов. Её не следует использовать для запуска асинхронного поведения, такого как вызовы 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
}

Ссылки и источники

Руководства

Производительность

Вопросы и ответы