mapState: options for reading state with connect">mapState: options for reading state with connect">
跳至主要内容

连接:使用 mapStateToProps 提取数据

作为传递给 connect 的第一个参数,mapStateToProps 用于从 store 中选择连接组件所需的那些数据。它通常简称为 mapState

  • 它在 store 状态发生变化时被调用。
  • 它接收整个 store 状态,并应该返回一个包含连接组件所需数据的对象。

定义 mapStateToProps

mapStateToProps 应该被定义为一个函数

function mapStateToProps(state, ownProps?)

它应该接受一个名为 state 的第一个参数,可选地接受一个名为 ownProps 的第二个参数,并返回一个包含连接组件所需数据的普通对象。

此函数应作为第一个参数传递给connect,并且将在 Redux 存储状态发生变化时每次被调用。如果您不想订阅存储,请将nullundefined传递给connect以代替mapStateToProps

使用function关键字(function mapState(state) { })或箭头函数(const mapState = (state) => { })编写mapStateToProps函数都没有关系 - 它们的效果相同。

参数

  1. state
  2. ownProps(可选)

state

mapStateToProps函数的第一个参数是整个 Redux 存储状态(与调用store.getState()返回的值相同)。因此,第一个参数通常被称为state。(虽然您可以随意命名参数,但将其命名为store将是不正确的 - 它是“状态值”,而不是“存储实例”。)

mapStateToProps函数应始终至少使用state作为参数编写。

// TodoList.js

function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)

ownProps(可选)

如果您的组件需要从其自身 props 中获取数据来检索存储中的数据,您可以使用第二个参数ownProps定义该函数。此参数将包含传递给由connect生成的包装组件的所有 props。

// Todo.js

function mapStateToProps(state, ownProps) {
const { visibilityFilter } = state
// ownProps would look like { "id" : 123 }
const { id } = ownProps
const todo = getTodoById(state, id)

// component receives additionally:
return { todo, visibilityFilter }
}

// Later, in your application, a parent component renders:
<ConnectedTodo id={123} />
// and your component receives props.id, props.todo, and props.visibilityFilter

您不需要将ownProps中的值包含在从mapStateToProps返回的对象中。connect将自动将这些不同的 props 源合并到最终的 props 集中。

返回值

您的mapStateToProps函数应返回一个包含组件所需数据的普通对象。

  • 对象中的每个字段都将成为您实际组件的 prop。
  • 字段中的值将用于确定您的组件是否需要重新渲染。

例如

function mapStateToProps(state) {
return {
a: 42,
todos: state.todos,
filter: state.visibilityFilter,
}
}

// component will receive: props.a, props.todos, and props.filter

注意:在您需要更多控制渲染性能的更高级场景中,mapStateToProps也可以返回一个函数。在这种情况下,该函数将用作特定组件实例的最终mapStateToProps。这允许您进行每实例记忆。有关更多详细信息,请参阅文档的高级用法:工厂函数部分,以及PR #279及其添加的测试。大多数应用程序都不需要这样做。

使用指南

mapStateToProps 重塑来自 Store 的数据

mapStateToProps 函数可以并且应该做的事情远不止 return state.someSlice它们有责任根据组件的需要“重塑”存储数据。 这可能包括将值作为特定 prop 名称返回,组合来自状态树不同部分的数据,以及以不同方式转换存储数据。

使用选择器函数提取和转换数据

我们强烈建议使用“选择器”函数来帮助封装从状态树中特定位置提取值的流程。记忆化的选择器函数在提高应用程序性能方面也起着关键作用(有关为什么以及如何使用选择器的更多详细信息,请参阅本页面的以下部分以及 高级用法:计算派生数据 页面)。

mapStateToProps 函数应该很快

每当存储发生变化时,所有已连接组件的所有 mapStateToProps 函数都会运行。因此,您的 mapStateToProps 函数应该尽可能快地运行。这也意味着缓慢的 mapStateToProps 函数可能是应用程序的潜在瓶颈。

作为“重塑数据”理念的一部分,mapStateToProps 函数经常需要以各种方式转换数据(例如过滤数组、将 ID 数组映射到其对应的对象,或从 Immutable.js 对象中提取纯 JS 值)。这些转换通常可能很昂贵,既包括执行转换的成本,也包括组件是否因此重新渲染。如果性能是一个问题,请确保这些转换仅在输入值发生变化时才运行。

mapStateToProps 函数应该纯净且同步

与 Redux reducer 类似,mapStateToProps 函数应该始终是 100% 纯净且同步的。它应该只接受 state(和 ownProps)作为参数,并返回组件所需的数据作为 props,而不会改变这些参数。它不应该用于触发异步行为,例如用于数据获取的 AJAX 调用,并且函数不应该声明为 async

mapStateToProps 和性能

返回值决定您的组件是否重新渲染

React Redux 在内部实现了 shouldComponentUpdate 方法,以便包装组件仅在组件所需的数据发生变化时才重新渲染。默认情况下,React Redux 使用 === 比较(“浅层相等”检查)来判断从 mapStateToProps 返回的对象的内容是否不同。如果任何字段发生了变化,那么您的组件将重新渲染,以便它可以接收更新后的值作为 props。请注意,返回相同引用的已变异对象是一个常见的错误,这会导致您的组件在预期时不重新渲染。

总结一下使用 mapStateToProps 从 store 中提取数据的 connect 包装的组件的行为

(state) => stateProps(state, ownProps) => stateProps
mapStateToProps 在以下情况下运行store state 发生变化store state 发生变化

ownProps 中的任何字段发生变化
组件在以下情况下重新渲染stateProps 中的任何字段发生变化stateProps 中的任何字段发生变化

ownProps 中的任何字段发生变化

仅在需要时返回新的对象引用

React Redux 使用浅比较来判断 mapStateToProps 的结果是否发生了变化。很容易在每次调用时意外返回新的对象或数组引用,这会导致即使数据实际上没有变化,组件也会重新渲染。

许多常见操作会导致创建新的对象或数组引用

  • 使用 someArray.map()someArray.filter() 创建新的数组
  • 使用 array.concat 合并数组
  • 使用 array.slice 选择数组的一部分
  • 使用 Object.assign 复制值
  • 使用扩展运算符 { ...oldState, ...newData } 复制值

将这些操作放在 记忆化选择器函数 中,以确保它们仅在输入值发生变化时运行。这将确保如果输入值没有发生变化,mapStateToProps 仍然会返回与之前相同的返回值,并且 connect 可以跳过重新渲染。

仅在数据发生变化时执行昂贵的操作

转换数据通常很昂贵(并且通常会导致创建新的对象引用)。为了使 mapStateToProps 函数尽可能快,您应该仅在相关数据发生变化时重新运行这些复杂的转换。

有几种方法可以解决这个问题

  • 一些转换可以在 action creator 或 reducer 中计算,并且转换后的数据可以保存在 store 中
  • 转换也可以在组件的 render() 方法中完成
  • 如果转换需要在 mapStateToProps 函数中完成,那么我们建议使用 记忆化选择器函数 来确保转换仅在输入值发生变化时运行。

Immutable.js 性能问题

Immutable.js 作者 Lee Byron 在 Twitter 上 明确建议在性能至关重要时避免使用 toJS

关于 #immutablejs 的性能提示:避免使用 .toJS() .toObject() 和 .toArray(),这些都是缓慢的完整复制操作,会使结构共享失效。

使用 Immutable.js 时,还有其他一些性能问题需要考虑 - 请参阅本页末尾的链接列表以获取更多信息。

行为和注意事项

mapStateToProps 如果存储状态相同,则不会运行

connect 生成的包装组件订阅了 Redux 存储。每次调度操作时,它都会调用 store.getState() 并检查 lastState === currentState。如果两个状态值在引用上相同,则它 *不会* 重新运行你的 mapStateToProps 函数,因为它假设存储状态的其余部分也没有改变。

Redux 的 combineReducers 实用程序函数试图对此进行优化。如果所有切片 reducer 都没有返回新值,则 combineReducers 会返回旧状态对象,而不是新对象。这意味着 reducer 中的变异会导致根状态对象未更新,因此 UI 不会重新渲染。

声明的参数数量会影响行为

仅使用 (state) 时,该函数会在根存储状态对象不同时运行。使用 (state, ownProps) 时,它会在存储状态不同时运行,并且在包装组件属性发生变化时也会运行。

这意味着 **除非你确实需要使用 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
}

教程

性能

问答