Connect: 使用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
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
存储状态改变时都会调用它。如果您不希望订阅存储,则传递null
或undefined
来代替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
state
ownProps
(optional)
state
mapStateToProps
函数的第一个参数是整个Redux
存储状态(与调用store.getstate()
返回的值相同)。因此,第一个参数传统上称为state
。(虽然你可以给这个参数取任何你想要的名字,但是把它叫做store
是不正确的——它是“state value
”,而不是“store instance
”。)
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
(optional)
如果组件需要从自己的道具中获取数据来从存储中检索数据,那么可以使用第二个参数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
您不需要在从mapStateToProps
返回的对象中包含来自ownProps
的值。连接将自动合并这些不同的道具来源到最后一套道具。
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.
使用指南
让mapStateToProps
重新构造存储中的数据
mapStateToProps
函数可以而且应该做的不仅仅是返回state.someSlice
。它们负责根据组件的需要“重塑”存储数据。这可能包括返回一个值作为特定的prop name
,组合来自状态树不同部分的数据片段,并以不同的方式转换存储数据。
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.
使用Selector
函数来提取和转换数据
我们强烈建议使用“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
函数应该很快
每当store
发生变化时,所有连接组件的所有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
函数应该是纯同步的
就像Redux reducer
一样,mapStateToProps
函数应该总是100%纯同步的。它应该只接受state
(和ownProps
)作为参数,并返回组件需要的数据作为props
,而不改变这些参数。它不应该被用来触发异步行为,比如AJAX
调用数据获取,函数也不应该被声明为异步。
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
and Performance
返回值决定组件是否重新呈现
React Redux
在内部实现shouldComponentUpdate
方法,以便包装器组件在组件需要的数据发生更改时准确地重新呈现。默认情况下,React Redux
决定从mapStateToProps
返回的对象的内容是否不同,使用===
比较(“浅相等”检查)返回对象的每个字段。如果任何字段发生了更改,那么组件将被重新呈现,以便它可以接收更新后的值作为道具。请注意,返回同一个引用的变异对象是一个常见的错误,它可能导致组件在预期的时候没有重新呈现。
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.
要总结由connect with mapStateToProps
包装的组件的行为,以便从存储中提取数据:
To summarize the behavior of the component wrapped by connect with mapStateToProps to extract data from the store:
(state) => stateProps |
(state, ownProps) => stateProps |
|
---|---|---|
mapStateToProps runs when: |
store state changes |
store state changes or any field of ownProps is different |
component re-renders when: | any field of stateProps is different |
any field of stateProps is different or any field of ownProps is different |
只在需要时返回新的对象引用
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.
许多常见的操作会导致创建新的对象或数组引用:
- 使用
someArray.map()
或someArray.filter()
创建新数组 - 使用
array.concat
合并数组 - 使用
array.slice
选择数组的一部分 - 使用
Object.assign
复制值 - 用扩展操作符
{…oldState,……newData}
Many common operations result in new object or array references being created:
- Creating new arrays with someArray.map() or someArray.filter()
- Merging arrays with array.concat
- Selecting portion of an array with array.slice
- Copying values with Object.assign
- 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.
仅在数据发生变化时执行开销较大的操作
数据转换通常代价高昂(通常会导致创建新的对象引用)。为了使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.
有几种方法可以解决这个问题:
- 某些转换可以在动作创建器或减速器中计算,转换后的数据可以保存在
store
中 - 转换也可以在组件的
render()
方法中完成 - 如果转换确实需要在
mapStateToProps
函数中完成,那么我们建议使用记忆选择器函数,以确保转换只在输入值发生变化时运行。There are a few ways to approach this:
- Some transformations could be calculated in an action creator or reducer, and the transformed data could be kept in the store
- Transformations can also be done in a component’s render() method
- 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 Performance Concerns
js
的作者Lee Byron
在Twitter
上明确建议,当考虑性能时,不要使用toJS
:
Immutable.js author Lee Byron on Twitter explicitly advises avoiding toJS when performance is a concern:
#immutablejs
的Perf
提示:避免 .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
将不会运行
由connect
生成的包装器组件订阅到Redux store
。每次分派一个操作时,它都会调用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.
声明参数的数量会影响行为
使用just (state)
,只要根store
的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
参数,除非你真的需要使用它,或者你的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) // {}
}
当函数的正式定义包含0
或2
个强制参数时,它将接收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
}