作者按照自己的理解加了一些代码
一、 Connect:使用mapStateToProps提取数据
mapStateToProps
是connect
的第一个参数,被用于从 Store 中选择 connected component 需要的部分数据。mapStateToProps
通常简称为 mapState
。
Store 中的 state 每次改变都会调用mapStateToProps
mapStateToProps
接受整个 store,并返回组件需要的数据对象
定义 mapStateToProps
mapStateToProps
应被定义为一个函数
function mapStateToProps(state, ownProps?)
复制代码
第一个参数叫做 state ,第二个参数是可选的,名为 ownProps,返回了一个包含 connected component 所需要数据的普通对象
mapStateToProps
是作为 connect
的第一个参数传入的,而且会在每次store
中的state改变时调用。
如果你不想在组件中订阅 store,在connect
的第一个参数位置(也就是mapStateToProps
的位置)传递null或者undefined就好了
mapStateToProps 的参数
- state
mapStateToProps
的第一个参数表示整个 Redux store 中存储的 state(其值和 store.getState()
返回的相同)。因此,第一个参数也被称为 state。 (不能叫做 store ,因为它是”state值”而不是”store实例”)
// TodoList.js
function mapStateToProps(state) {
const { todos } = state
return { todoList: todos.allIds }
}
export default connect(mapStateToProps)(TodoList)
复制代码
// TodoList.js, 还有一种比较简洁的写法
export default connect(
state => ({todoList:state.todos.allIds})
)(TodoList)
复制代码
- ownProps(可选)
如果你的组件想用自己的props数据来从 store 中检索数据,你可以使用第二个参数 ownProps 来定义mapStateProps
函数。ownProps 包含了所有props,这些props提供给 connect
生成的 wrapper component
// Todo.js
function mapStateToProps(state, ownProps) {
const { visibilityFilter } = state
// ownProps类似于{ "id" : 123 }这样的形式
const { id } = ownProps
const todo = getTodoById(state, id)
// Todo.js组件额外收到
return { todo, visibilityFilter }
}
// 然后,一个父组件会呈现
;<ConnectedTodo id={123} />
// 你的组件会接收到 props.id, props.todo, and props.visibilityFilter
复制代码
你不需要把来自 ownProps 的值包含在从 mapStateToProps
返回的对象中。因为 connect
会自动整合这些不同的props,形成一个最终的props集合
mapStateProps 的 Return
mapStateProps
返回的是一个包含其所在组件所需数据的普通对象
- 对象中的每个字段都将成为所在组件中props
- 字段中的值将用于确定您的组件是否需要重新渲染
function mapStateToProps(state) {
return {
a: 42,
todos: state.todos,
filter: state.visibilityFilter,
}
}
// 组件会收到: props.a, props.todos, and props.filter
复制代码
- 注意
在需要更多控制渲染性能的高级场景中,mapStateToProps
也可以返回一个函数。在这种情况下,该函数将用作特定组件实例的最终 mapStateToProps
。你可以进行 per-instance memoization,详情可看Advanced Usage:Computting Derived Data
mapStateToProps使用指南
让mapStateToProps
重塑Store
中的数据
mapStateToProps
函数应该做的不仅仅是返回 state.someSlice
。他们有责任根据该组件的需要来重新塑造 store 中的数据。这可能包括返回一个值作为特定的 props 名字、组合来自状态树不同部分的数据片段以及以不同方式转换 store 数据。
使用selector function
提取和转换数据
强烈鼓励使用 selector function
来帮助封装从状态树中的特定位置提取值的过程。 Memoized selector functions
在提高应用程序性能方面也发挥着关键作用(请参阅:计算派生数据页面 Advanced Usage: Computing Derived Data )
mapStateToProps
函数应该是快速的
每当 store 发生变化时,所有连接组件的所有 mapStateToProps
函数都会运行。因此,您的 mapStateToProps
函数应该尽可能快地运行。这也意味着缓慢的 mapStateToProps
函数可能成为您的应用程序的潜在瓶颈。
作为“重塑数据”(”re-shaping data”)思想的一部分,mapStateToProps
函数经常需要以各种方式转换数据(例如过滤数组、将 ID 数组映射到其对应的对象,或从 Immutable.js 对象中提取纯 JS 值) .这些转换通常很耗时,无论是执行转换的成本还是组件是否因此重新渲染。如果性能是一个问题,请确保仅在输入值发生更改时才运行这些转换。
mapStateToProps
函数应是纯函数和同步函数
就像 Redux reducer 一样,mapStateToProps
函数应该始终是 100% 纯和同步的。它应该只接受 state(和 ownProps)作为参数,并返回组件需要的数据作为 props 而不改变这些参数。它不应用于触发异步行为,如 AJAX 调用以获取数据,并且不应将函数声明为异步。
mapStateToProps
和性能
返回值决定你的组件是否重新渲染
React Redux 在内部实现了 shouldComponentUpdate
方法,这样包装器组件就会在组件需要的数据发生变化时准确地重新渲染。
默认情况下,React Redux 通过对返回对象的每个字段使用 === 比较(“浅相等”检查)来决定从 mapStateToProps
返回的对象的内容是否不同。如果任何字段发生更改,则您的组件将被重新渲染,以便它可以接收更新的值作为 props。请注意,返回相同引用的变异对象是一个常见错误(可能是reducers是不纯函数导致的错误),它可能导致您的组件无法按预期重新渲染。
(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 }
将这些操作放在记忆化的选择器函数(selector function)中,以确保它们仅在输入值发生更改时运行。这也将确保如果输入值没有改变,mapStateToProps
仍将返回与之前相同的结果值,并且 connect 可以跳过重新渲染。
仅在数据更改时执行代价高(expensive)的操作
转换数据通常很昂贵(并且通常会导致创建新的对象引用)。你应该只在相关数据发生变化时重新运行这些复杂的转换。
有几种方法可以解决这个问题:
- 一些转换可以在 action creator 或 reducer 中计算,转换后的数据可以保存在 store 中
- 转换也可以在组件的 render() 方法中完成
- 如果转换确实需要在
mapStateToProps
函数中完成,那么我们建议使用记忆化选择器函数来确保转换仅在输入值更改时运行。
Immutable.js 性能问题
- Immutable.js 作者 Lee Byron 在 Twitter 上明确建议在考虑性能时避免使用 toJS:
- #immutablejs 的性能提示:避免 .toJS() .toObject() 和 .toArray() 所有使结构共享无用的缓慢完整复制操作。
行为和陷阱
如果Store State相同,mapStateToProps 将不会运行
connect
生成的包装器组件订阅 Redux store。每次分派动作时,它都会调用 store.getState() 并检查是否 lastState === currentState。如果两个状态值通过引用相同,那么它不会重新运行您的 mapStateToProps
函数,因为它假定Store State的其余部分也没有改变。
Redux combineReducers 实用程序函数尝试为此进行优化。如果slice reducers 都没有返回新值,则 combineReducers 返回旧状态对象而不是新值。这意味着reducer 中的变异会导致根状态对象不被更新,因此 UI 不会重新渲染。
二、Connect: 用mapDispatchToProps来分派操作行为(Dispatching Actions)
作为传递给 connect
的第二个参数,mapDispatchToProps
用于将操作分派到store。
dispatch
是 Redux store 的一个函数。你调用 store.dispatch 来调度一个动作。这是触发状态更改的唯一方法。
使用 React Redux,你的组件永远不会直接访问store——connect 会为你做这件事。 React Redux 为您提供了两种让组件调度操作的方法:
- 默认情况下,a connected component 接收 props.dispatch 并且可以自己调度动作。
connect
可以接受一个名为mapDispatchToProps
的参数,它允许您创建调用时调度的函数,并将这些函数作为 props 传递给您的组件。
mapDispatchToProps
函数通常简称为 mapDispatch
,但实际使用的变量名称可以是您想要的任何名称。
Dispatching方法
dispatch 作为 Prop
如果您没有为 connect() 指定第二个参数,您的组件将默认接收dispatch。例如:
connect()(MyComponent)
connect(null, null)(MyComponent)
connect(mapStateToProps /** no second argument */)(MyComponent)
复制代码
一旦你以这种方式 connect 你的组件,你的组件就会收到 props.dispatch。您可以使用它来将操作分派到store。
function Counter({ count, dispatch }) {
return (
<div>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<span>{count}</span>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'RESET' })}>reset</button>
</div>
)
}
复制代码
提供 mapDispatchToProps 参数
提供 mapDispatchToProps 允许你指定组件可能需要调度的操作。它允许你提供动作调度功能作为道具。
因此,您可以直接调用 props.increment() 而不是调用 props.dispatch(() => increment())。您可能想要这样做的原因有几个。
- 更具声明性
首先,将调度逻辑封装到函数中使实现更具声明性。调度一个动作(Dispatching an action)并让 Redux store 处理数据流的重点是 “如何实现行为” ,而不是“它做什么”。
一个很好的例子是在单击按钮时分派一个动作。直接连接按钮在概念上可能没有意义,也没有dispatch的按钮参考。
// button需要意识到"dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />
// button没意识到"dispatch",
<button onClick={doSomething} />
复制代码
一旦你用分派动作的函数包装了所有的action creators ,这个组件就不需要分派了。因此,如果您定义自己的 mapDispatchToProps
,则连接的组件将不再接收调度。
- 将动作调度逻辑传递给(未连接的)子组件
此外,还可以获得将动作分派功能传递给子(unconnected )组件的能力。这允许更多组件去执行分派操作,同时让它们“没有意识到”Redux的存在。
// 将 toggleTodo 传递给子组件
// 使 Todo 能够调度 toggleTodo 动作
const TodoList = ({ todos, toggleTodo }) => (
<div>
{todos.map((todo) => (
<Todo todo={todo} onClick={toggleTodo} />
))}
</div>
)
复制代码
这就是 React Redux 的 connect 所做的——它封装了与 Redux store 对话的逻辑。
mapDispatchToProps
的两种形式
mapDispatchToProps
参数可以有两种形式。函数形式允许更多的定制,对象形式易于使用。
- 函数形式:允许更多的自定义,获得对 dispatch 和可选的 ownProps 的访问
- 对象简写形式:更具声明性且更易于使用
注意:我们建议使用 mapDispatchToProps
的对象形式,除非您特别需要以某种方式自定义调度行为。
mapDispatchToProps
的函数形式
将 mapDispatchToProps
定义为函数可以在自定义组件接收的函数以及它们如何调度操作方面具有最大的灵活性。你可以访问 dispatch 和 ownProps。您可以利用这个机会编写自定义函数以供你连接的组件调用。
参数
- dispatch
- ownProps (optional)
- dispatch
mapDispatchToProps
函数将以 dispatch 作为第一个参数被调用。通常会通过返回在其内部调用 dispatch() 的新函数来使用它,并且可以直接传入一个普通的action object或传入一个 action creator的结果。
const mapDispatchToProps = (dispatch) => {
return {
// 派发普通 actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
复制代码
也可以将参数转发给action creator:
const mapDispatchToProps = (dispatch) => {
return {
// 显式转发参数
onClick: (event) => dispatch(trackClick(event)),
// 隐式转发参数
onReceiveImpressions: (...impressions) =>
dispatch(trackImpressions(impressions)),
}
}
复制代码
- ownProps(可选)
如果你的 mapDispatchToProps
函数被声明为带两个参数,它会以 dispatch 作为第一个参数和传递给connected component的 props 作为第二个参数被调用,并且会在connected component接收到新的 props 时重新调用。
这意味着,你可以在组件的 props 更改时这样做,而不是在组件重新渲染时将新的 props 重新绑定到 action dispatchers。
- 新的props绑定在组件装载上
render() {
return <button onClick={() => this.props.toggleTodo(this.props.todoId)} />
}
const mapDispatchToProps = dispatch => {
return {
toggleTodo: todoId => dispatch(toggleTodo(todoId))
}
}
复制代码
- 新的props绑定在props改变中
render() {
return <button onClick={() => this.props.toggleTodo()} />
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
toggleTodo: () => dispatch(toggleTodo(ownProps.todoId))
}
}
复制代码
Return
mapDispatchToProps
函数应该返回一个普通对象:
- 对象中的每个字段(key)都将成为该组件的独立prop,并且该值(value)通常应该是一个在调用时分派动作的函数。
- 如果在调度中使用 action creators ,则约定简单地将 key 命名为与动作创建者相同的名称:
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
const mapDispatchToProps = (dispatch) => {
return {
// action creators 返回的 dispatching actions
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
}
}
复制代码
mapDispatchToProps
函数的返回值将作为props合并到 connected component 中。你可以直接调用 返回值 来调度组件的actions。
function Counter({ count, increment, decrement, reset }) {
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={reset}>reset</button>
</div>
)
}
复制代码
用 bindActionCreators
定义 mapDispatchToProps
函数
手动封装这些函数很乏味,所以 Redux 提供了一个函数来简化它——bindActionCreators
。
bindActionCreators
将一个值为 action creators 的对象转换为具有相同key的对象,但每个 action creators 都包装在一个dispatch调用中,以便可以直接调用它们。
bindActionCreators
接受两个参数:
- 一个函数(an action creator)或一个对象(每个字段都是一个动作创建者)
- dispatch
bindActionCreators
生成的包装函数将自动转发它们的所有参数,因此你无需手动执行此操作。
import { bindActionCreators } from 'redux'
const increment = () => ({ type: 'INCREMENT' })
const decrement = () => ({ type: 'DECREMENT' })
const reset = () => ({ type: 'RESET' })
// 绑定单个action creator
// returns (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)
// 绑定一个由action creators组成的对象
const boundActionCreators = bindActionCreators(
{ increment, decrement, reset },
dispatch
)
// returns
// {
// increment: (...args) => dispatch(increment(...args)),
// decrement: (...args) => dispatch(decrement(...args)),
// reset: (...args) => dispatch(reset(...args)),
// }
复制代码
在mapDispatchToProps
函数中使用 bindActionCreators
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ increment, decrement, reset }, dispatch)
}
// 组件接收 props.increment, props.decrement, props.reset
connect(null, mapDispatchToProps)(Counter)
复制代码
手动注入 dispatch
如果提供了 mapDispatchToProps
参数,组件将不再接收默认调度。您可以通过手动将其添加到 mapDispatchToProps
的返回中来将其带回来,尽管大多数时候你不需要这样做:
import { bindActionCreators } from 'redux'
// ...
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ increment, decrement, reset }, dispatch),
}
}
复制代码
将 mapDispatchToProps
定义为一个对象
你已经看到在 React 组件中调度 Redux 动作的设置遵循一个非常相似的过程:定义一个动作创建者,将它包装在另一个看起来像 (…args) => dispatch(actionCreator(…args)) 的函数中,然后将该包装函数作为prop传递给组件。
因为这很常见,connect 支持 mapDispatchToProps
参数的 “对象简写” 形式:如果你传递一个由action creators组成的对象,而不是一个函数,connect 将在内部自动为你调用 bindActionCreators
。
建议始终使用 mapDispatchToProps
的“对象简写”形式
注意:
- mapDispatchToProps 对象的每个字段都被假定为一个action creator
- 您的组件将不再作为props接收dispatch
// React Redux 为你自动做了以下代码
;(dispatch) => bindActionCreators(mapDispatchToProps, dispatch)
// 所以你只需把mapDispatchToProps简写为
const mapDispatchToProps = {
increment,
decrement,
reset,
}
复制代码
由于变量的实际名称由你,你希望给它一个类似 actionCreators 的名称,或者甚至在调用 connect 时内联定义对象:
import { increment, decrement, reset } from './counterActions'
const actionCreators = {
increment,
decrement,
reset,
}
export default connect(mapState, actionCreators)(Counter)
// or
export default connect(mapState, { increment, decrement, reset })(Counter)
复制代码
常见问题
为什么我的组件没有收到dispatch?
TypeError: this.props.dispatch is not a function
复制代码
这是当您尝试调用 this.props.dispatch 时发生的常见错误,但 dispatch 并未注入到您的组件中。
仅在以下情况下 dispatch 才会注入到您的组件中:
- 未提供
mapDispatchToProps
默认的 mapDispatchToProps
只是 dispatch => ({ dispatch })。如果您不提供 mapDispatchToProps
,则将提供如上所述的调度。
// 换句话说,会变成这样
// component receives `dispatch`
connect(mapStateToProps /** no second argument*/)(Component)
复制代码
- 自定义的mapDispatchToProps函数返回具体包含dispatch
可以通过提供自定义的 mapDispatchToProps 函数来恢复dispatch
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement()),
reset: () => dispatch(reset()),
dispatch,
}
}
复制代码
或者,使用 bindActionCreators:
import { bindActionCreators } from 'redux'
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ increment, decrement, reset }, dispatch),
}
}
复制代码
我可以在 Redux 中没有 mapStateToProps 的时候mapDispatchToProps吗?
yes!你可以通过传递undefined或null来跳过第一个参数,组件将不会订阅store,且仍然能收到mapdispatchToProps定义的dispatch props
connect(null, mapDispatchToProps)(MyComponent)
复制代码