Skip to main content

连接:使用 mapStateToProps 提取数据

¥Connect: Extracting Data with mapStateToProps

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

¥As the first argument passed in to connect, mapStateToProps is used for selecting the part of the data from the store that the connected component needs. It’s frequently referred to as just mapState for short.

  • 每次存储状态发生变化时都会调用它。

    ¥It is called every time the store state changes.

  • 它接收整个存储状态,并且应该返回该组件所需的数据对象。

    ¥It receives the entire store state, and should return an object of data this component needs.

定义 mapStateToProps

¥Defining mapStateToProps

mapStateToProps 应该被定义为一个函数:

¥mapStateToProps should be defined as a function:

function mapStateToProps(state, ownProps?)

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

¥It should take a first argument called state, optionally a second argument called ownProps, and return a plain object containing the data that the connected component needs.

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

¥This function should be passed as the first argument to connect, and will be called every time when the Redux store state changes. If you do not wish to subscribe to the store, pass null or undefined to connect in place of mapStateToProps.

mapStateToProps 函数是使用 function 关键字 (function mapState(state) { } ) 还是箭头函数 (const mapState = (state) => { } ) 编写并不重要 - 不管怎样,它的工作原理都是一样的。

¥It does not matter if a mapStateToProps function is written using the function keyword (function mapState(state) { } ) or as an arrow function (const mapState = (state) => { } ) - it will work the same either way.

参数

¥Arguments

  1. state

  2. ownProps(可选)

    ¥ownProps (optional)

state

mapStateToProps 函数的第一个参数是整个 Redux 存储状态(与调用 store.getState() 返回的值相同)。因此,第一个参数传统上称为 state。(虽然你可以为参数指定任何名称,但将其称为 store 是不正确的 - 这是 "状态值",而不是 "存储实例"。)

¥The first argument to a mapStateToProps function is the entire Redux store state (the same value returned by a call to store.getState()). Because of this, the first argument is traditionally just called state. (While you can give the argument any name you want, calling it store would be incorrect - it's the "state value", not the "store instance".)

编写 mapStateToProps 函数时应始终至少传入 state

¥The mapStateToProps function should always be written with at least state passed in.

// TodoList.js

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

export default connect(mapStateToProps)(TodoList)

ownProps(可选)

¥ownProps (optional)

如果你的组件需要来自其自己的 props 的数据来从存储中检索数据,你可以使用第二个参数 ownProps 定义该函数。该参数将包含给予 connect 生成的封装器组件的所有 props。

¥You may define the function with a second argument, ownProps, if your component needs the data from its own props to retrieve data from the store. This argument will contain all of the props given to the wrapper component that was generated by connect.

// 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 会自动将这些不同的属性来源合并到最终的一组属性中。

¥You do not need to include values from ownProps in the object returned from mapStateToProps. connect will automatically merge those different prop sources into a final set of props.

返回

¥Return

你的 mapStateToProps 函数应返回一个普通对象,其中包含组件所需的数据:

¥Your mapStateToProps function should return a plain object that contains the data the component needs:

  • 对象中的每个字段都将成为你实际组件的属性

    ¥Each field in the object will become a prop for your actual component

  • 字段中的值将用于确定你的组件是否需要重新渲染

    ¥The values in the fields will be used to determine if your component needs to re-render

例如:

¥For example:

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 及其添加的测试。大多数应用永远不需要这个。

¥Note: In advanced scenarios where you need more control over the rendering performance, mapStateToProps can also return a function. In this case, that function will be used as the final mapStateToProps for a particular component instance. This allows you to do per-instance memoization. See the Advanced Usage: Factory Functions section of the docs for more details, as well as PR #279 and the tests it adds. Most apps never need this.

使用指南

¥Usage Guidelines

mapStateToProps 重塑存储中的数据

¥Let mapStateToProps Reshape the Data from the Store

mapStateToProps 函数可以而且应该比 return state.someSlice 做更多的事情。他们有责任根据该组件的需要存储 "re-shaping" 数据。这可能包括返回一个值作为特定的 prop 名称、组合来自状态树不同部分的数据片段以及以不同的方式转换存储数据。

¥mapStateToProps functions can, and should, do a lot more than just return state.someSlice. They have the responsibility of "re-shaping" store data as needed for that component. This may include returning a value as a specific prop name, combining pieces of data from different parts of the state tree, and transforming the store data in different ways.

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

¥Use Selector Functions to Extract and Transform Data

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

¥We highly encourage the use of "selector" functions to help encapsulate the process of extracting values from specific locations in the state tree. Memoized selector functions also play a key role in improving application performance (see the following sections in this page and the Advanced Usage: Computing Derived Data page for more details on why and how to use selectors.)

mapStateToProps 函数应该很快

¥mapStateToProps Functions Should Be Fast

每当存储发生更改时,所有连接组件的所有 mapStateToProps 函数都将运行。因此,你的 mapStateToProps 函数应该尽可能快地运行。这也意味着缓慢的 mapStateToProps 函数可能成为应用的潜在瓶颈。

¥Whenever the store changes, all of the mapStateToProps functions of all of the connected components will run. Because of this, your mapStateToProps functions should run as fast as possible. This also means that a slow mapStateToProps function can be a potential bottleneck for your application.

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

¥As part of the "re-shaping data" idea, mapStateToProps functions frequently need to transform data in various ways (such as filtering an array, mapping an array of IDs to their corresponding objects, or extracting plain JS values from Immutable.js objects). These transformations can often be expensive, both in terms of cost to execute the transformation, and whether the component re-renders as a result. If performance is a concern, ensure that these transformations are only run if the input values have changed.

mapStateToProps 函数应该是纯函数和同步的

¥mapStateToProps Functions Should Be Pure and Synchronous

就像 Redux reducer 一样,mapStateToProps 函数应该始终是 100% 纯且同步的。它应该只接受 state (和 ownProps)作为参数,并返回组件需要的数据作为 props,而不改变这些参数。它不应该被用来触发异步行为,比如 AJAX 调用数据获取,并且函数不应该被声明为 async

¥Much like a Redux reducer, a mapStateToProps function should always be 100% pure and synchronous. It should only take state (and ownProps) as arguments, and return the data the component needs as props without mutating those arguments. It should not be used to trigger asynchronous behavior like AJAX calls for data fetching, and the functions should not be declared as async.

mapStateToProps 和性能

¥mapStateToProps and Performance

返回值确定你的组件是否重新渲染

¥Return Values Determine If Your Component Re-Renders

React Redux 在内部实现了 shouldComponentUpdate 方法,以便当组件所需的数据发生更改时,封装器组件可以精确地重新渲染。默认情况下,React Redux 通过对返回对象的每个字段进行 === 比较("浅层平等" 检查)来确定从 mapStateToProps 返回的对象的内容是否不同。如果任何字段发生更改,那么你的组件将重新渲染,以便它可以接收更新的值作为 props。请注意,返回同一引用的修改对象是一个常见错误,可能会导致组件无法按预期重新渲染。

¥React Redux internally implements the shouldComponentUpdate method such that the wrapper component re-renders precisely when the data your component needs has changed. By default, React Redux decides whether the contents of the object returned from mapStateToProps are different using === comparison (a "shallow equality" check) on each fields of the returned object. If any of the fields have changed, then your component will be re-rendered so it can receive the updated values as props. Note that returning a mutated object of the same reference is a common mistake that can result in your component not re-rendering when expected.

总结一下 connectmapStateToProps 封装的组件从存储中提取数据的行为:

¥To summarize the behavior of the component wrapped by connect with mapStateToProps to extract data from the store:

(state) => stateProps(state, ownProps) => stateProps
mapStateToProps 在以下情况下运行:存储 state 更改store state 改变

ownProps 的任何字段不同
组件在以下情况下重新渲染:stateProps 的任何字段都不同stateProps 任意字段不同

ownProps 任意字段不同

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

¥Only Return New Object References If Needed

React Redux 进行浅比较以查看 mapStateToProps 结果是否发生了变化。每次很容易意外返回新的对象或数组引用,这会导致组件重新渲染,即使数据实际上是相同的。

¥React Redux does shallow comparisons to see if the mapStateToProps results have changed. It’s easy to accidentally return new object or array references every time, which would cause your component to re-render even if the data is actually the same.

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

¥Many common operations result in new object or array references being created:

  • 使用 someArray.map()someArray.filter() 创建新数组

    ¥Creating new arrays with someArray.map() or someArray.filter()

  • 将数组与 array.concat 合并

    ¥Merging arrays with array.concat

  • 使用 array.slice 选择数组的一部分

    ¥Selecting portion of an array with array.slice

  • 使用 Object.assign 复制值

    ¥Copying values with Object.assign

  • 使用扩展运算符 { ...oldState, ...newData } 复制值

    ¥Copying values with the spread operator { ...oldState, ...newData }

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

¥Put these operations in memoized selector functions to ensure that they only run if the input values have changed. This will also ensure that if the input values haven't changed, mapStateToProps will still return the same result values as before, and connect can skip re-rendering.

仅在数据更改时执行昂贵的操作

¥Only Perform Expensive Operations When Data Changes

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

¥Transforming data can often be expensive (and usually results in new object references being created). In order for your mapStateToProps function to be as fast as possible, you should only re-run these complex transformations when the relevant data has changed.

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

¥There are a few ways to approach this:

  • 一些转换可以在动作创建器或 reducer 中计算,并且转换后的数据可以保存在存储中

    ¥Some transformations could be calculated in an action creator or reducer, and the transformed data could be kept in the store

  • 转换也可以在组件的 render() 方法中完成

    ¥Transformations can also be done in a component's render() method

  • 如果确实需要在 mapStateToProps 函数中完成转换,那么我们建议使用 记忆选择器功能 以确保仅在输入值发生更改时才运行转换。

    ¥If the transformation does need to be done in a mapStateToProps function, then we recommend using memoized selector functions to ensure the transformation is only run when the input values have changed.

Immutable.js 性能问题

¥Immutable.js Performance Concerns

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

¥Immutable.js author Lee Byron on Twitter explicitly advises avoiding toJS when performance is a concern:

#immutablejs 的性能提示:避免 .toJS() .toObject() 和 .toArray() 所有缓慢的完整复制操作,这些操作会导致结构共享毫无用处。

¥Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless.

Immutable.js 还有其他几个性能问题需要考虑 - 有关详细信息,请参阅本页末尾的链接列表。

¥There's several other performance concerns to take into consideration with Immutable.js - see the list of links at the end of this page for more information.

行为和陷阱

¥Behavior and Gotchas

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

¥mapStateToProps Will Not Run if the Store State is the Same

connect 生成的封装器组件订阅 Redux 存储。每次分派一个操作时,它都会调用 store.getState() 并检查 lastState === currentState 是否存在。如果两个状态值通过引用相同,则它将不会重新运行 mapStateToProps 函数,因为它假设存储状态的其余部分也没有更改。

¥The wrapper component generated by connect subscribes to the Redux store. Every time an action is dispatched, it calls store.getState() and checks to see if lastState === currentState. If the two state values are identical by reference, then it will not re-run your mapStateToProps function, because it assumes that the rest of the store state hasn't changed either.

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

¥The Redux combineReducers utility function tries to optimize for this. If none of the slice reducers returned a new value, then combineReducers returns the old state object instead of a new one. This means that mutation in a reducer can lead to the root state object not being updated, and thus the UI won't re-render.

声明的参数数量影响行为

¥The Number of Declared Arguments Affects Behavior

仅使用 (state),只要根存储状态对象不同,该函数就会运行。对于 (state, ownProps),它会在存储状态不同时运行,并且在封装器属性发生更改时也会运行。

¥With just (state), the function runs whenever the root store state object is different. With (state, ownProps), it runs any time the store state is different and ALSO whenever the wrapper props have changed.

这意味着除非你确实需要使用 ownProps 参数,否则你不应添加 ownProps 参数,否则你的 mapStateToProps 函数将运行得比需要的更频繁。

¥This means that you should not add the ownProps argument unless you actually need to use it, or your mapStateToProps function will run more often than it needs to.

此行为存在一些边缘情况。强制参数的数量决定了 mapStateToProps 是否会收到 ownProps

¥There are some edge cases around this behavior. The number of mandatory arguments determines whether mapStateToProps will receive ownProps.

如果函数的正式定义包含一个强制参数,则 mapStateToProps 将不会收到 ownProps

¥If the formal definition of the function contains one mandatory parameter, mapStateToProps will not receive 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

¥It will receive ownProps when the formal definition of the function contains zero or two mandatory parameters:

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
}

¥Links and References

教程

¥Tutorials

表现

¥Performance

Q&A