连接:使用 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 存储状态发生变化时都会被调用。如果你不想订阅存储,请将 null
或 undefined
传递给 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
state
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 finalmapStateToProps
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.
总结一下 connect
和 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 在以下情况下运行: | 存储 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()
orsomeArray.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
¥Practical Redux Series, Part 6: Connected Lists, Forms, and Performance
¥Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance
表现
¥Performance
Lee Byron 的推文建议为了性能避免使用
toJS
、toArray
和toObject
¥Lee Byron's Tweet Suggesting to avoid
toJS
,toArray
andtoObject
for Performance
Q&A