连接:使用 mapStateToProps
提取数据
作为传递给 connect
的第一个参数,mapStateToProps
用于从 store 中选择连接组件所需的那些数据。它通常简称为 mapState
。
- 它在 store 状态发生变化时被调用。
- 它接收整个 store 状态,并应该返回一个包含连接组件所需数据的对象。
定义 mapStateToProps
mapStateToProps
应该被定义为一个函数
function mapStateToProps(state, ownProps?)
它应该接受一个名为 state
的第一个参数,可选地接受一个名为 ownProps
的第二个参数,并返回一个包含连接组件所需数据的普通对象。
此函数应作为第一个参数传递给connect
,并且将在 Redux 存储状态发生变化时每次被调用。如果您不想订阅存储,请将null
或undefined
传递给connect
以代替mapStateToProps
。
使用function
关键字(function mapState(state) { }
)或箭头函数(const mapState = (state) => { }
)编写mapStateToProps
函数都没有关系 - 它们的效果相同。
参数
state
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
}
链接和引用
教程
性能
问答