写在前面
在前面介绍了 Redux,并用原生的 Redux 配合 React 开发了一个 todo-list 小 demo。
但是后来 React 的作者为了让 React 用户更好地使用 Redux 进行 React 的项目开发,在 Redux 的基础上封装了一个专用库:React-Redux。React-Redux 在 Redux 的基础上根据 React 的特性进行了优化,因此现在大部分的 React 的项目开发的状态管理工具都是 React-Redux,但使用 React-Redux 的前提还是要学会 Redux,因为 React-Redux 是在 Redux 的基础上提供了额外的一个方法和一个组件。
React-Redux 的厉害之处不是说额外提供了一个方法和一个组件,而是其提出了一种新的开发思路。在仅仅使用 Redux 的过程中,我们的组件在获得 state 后要在组件内部代码中筛选出该组件需要的数据,在组件调用事件函数时 dispatch 改变状态的动作。而 React-Reudx 提供了一种新的思路,将组件划分为 UI 组件 和 容器组件。
UI 组件仅仅负责界面的展示,不负责任何的数据处理操作,因此不能在 UI 组件里使用 Redux 的 API ,如 getState、dispatch 等等。那么 UI 组件应该如何使用动态的数据状态呢?答案是使用组件的 props 参数,UI 组件用到的所有数据都在 Props 中接收到。
与 UI 组件相反,容器组件负责数据状态的获得和操作,处理业务逻辑,如 getState、dispatch 等,不负责 UI 的呈现。
将处理数据的容器组件包裹住只负责页面呈现的 UI 组件,二者包裹在一起就形成了一个完整的带有数据业务逻辑的组件,这样一来,状态处理 和 UI 呈现分离,更容易管理状态和 UI 样式,代码更加结构化。
那么 React-Redux 如何使用呢?
1. connect 方法
React-Redux 提供了 connect 方法用于将 UI 组件和 容器组件 组合起来。connect 是一个返回一个函数的方法。该方法接收两个参数:mapStateToProps 和 mapDispatchToProps,返回的函数接收一个 UI 组件,并返回一个已经包裹了 UI 组件的容器组件。包裹了 UI 组件的容器组件就是一个包含状态的完整的组件。
因此其实将 UI 组件和 容器组件 分开来说不太合适,将 UI 组件通过 connect 方法包裹一层状态业务逻辑就是容器组件了,因此,在页面中加载组件时,不再是使用 UI 组件了,而是使用包裹了状态业务逻辑的 容器组件。
那么这个状态的业务逻辑是如何告诉负责包裹 UI 组件的 connect 方法的呢? 就是通过给 connect 方法传递参数 mapStateToProps 和 mapDispatchToProps 实现的。
const container = connect(
mapStateToProps,
mapDispatchToProps
)(VisibleTodoList)
1.1 mapStateToProps
mapStateToProps 字面意思就是映射状态到属性,就是说其负责将 Redux 中获得的状态 state 通过处理得到该组件需要的数据状态,然后映射到 UI 组件的 Props 属性上。
这种映射方式就是通过对象的键值对实现的,但是如果 mapStateToProps 直接是一个对象,那在该对象中对 Redux 状态的获取还得通过 getState() 手动获取,既然肯定是要获取当前状态,不如 Reac-Redux 帮我们做好算了。因此 React-Redux 就将 mapStateToProps 规定为是一个返回对象的函数,该函数接收两个参数 state 和 ownProps,其中 state 就是当前的状态,React-Redux 会在 Redux 的状态变化时自动调用 mapStateToPrps 的,并将当前的状态作为第一个参数传递。
那么第二个参数 ownProps 是什么呢?听字母的意思就是自己的属性的意思,容器组件在页面中加载时,页面也可以通过属性的方式传递数据给该容器组件,就像之前组件接收属性作为 Props 一样,ownProps就是容器组件在使用时,通过组件传递属性的方式接收的自己的数据状态。
因此我们知道,mapStateToProps 订阅了 Redux 的 store,当状态发生变化时会自动执行,重新计算数据,从而重新渲染页面。但是还有一种方式会使该函数重新执行,就是其依赖的 ownProps 发生变化时也会重新计算并渲染。
通过在 connect 方法中传递 mapStateToProps,connect 方法就会将 mapStateToprops 函数返回的对象中的全部属性作为 connect 连接的 UI 组件的 Props 对象的属性传递给 UI 组件的。因此在 UI 组件中的 Props 可以直接拿到 mapStateToState 返回对象里的属性。
要点总结:
1. mapStateToProps 用于外部向 UI 组件传递数据状态
2. mapStateToProps 是一个函数,接收 state 和 ownProps 两个参数
3. mapStateToProps 返回一个对象,对象的全部属性可在其连接的 UI 组件的 Props 对象中拿到
4. mapStateToProps 订阅了 store 和 ownProps
const mapStateToProps = (state, ownProps)=>{
return {
todoList: getVisibleTodoList(state.todoList, state.filter)
}
}
1.2 mapDispatchToProps
mapStateToProps 是负责外部传递数据到 UI 组件内使用的,那如果 UI 组件想要改变外部状态的时候该怎么办呢?
在 Redux 中就是通过在事件函数中使用 store.dispatch(action)
方法触发改变状态的动作。React-Redux 规定了,在 UI 组件中不要包含业务逻辑的处理,不要在 UI 组件中使用 Redux API。
ok,那就将该事件处理函数定义在容器组件的业务逻辑层中,这负责 UI 组件向外传递数据操作的就是 mapDispatchToprops。
mapDispatchToProps 字面意思就是映射派遣到属性。就是将 UI 组件中的事件处理函数映射到 UI 组件的 Props 对象中,UI 组件在注册事件回调函数时直接使用 Props 中传递来的事件处理函数值就可以。
一般在 UI 组件的事件处理函数中都会涉及到对 Redux 中 state 的改变,因此会经常用到 store.dispatch,但是使用 dispatch 方法做的只有一件事就是接收一个 action 然后改变状态。因此 React-Redux 规定,mapDispatchToProps 可以是一个对象,也可以是一个函数。
当 mapDispatchToProps 是一个对象时,键值对中的值必须是一个 Action Creator 函数。会由 React-Redux 自动 dispatch。 如下:
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
当 mapDispatchToProps 是一个函数时,需要返回一对象,对象的键值对中的值必须是一个手动 dispatch 的函数。如下:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
要点总结:
1. mapDispatchToProps 负责 UI 组件向外部传递数据操作。
2. mapDispatchToProps 可以是一个对象,也可以是一个函数。
3. mapDispatchToProps 对象或返回对象的属性可以在 UI 组件的 Props 中拿到
2. <Provide>
组件
使用 connect 方法生成的 容器组件 的正常使用依赖于 Redux 的仓库实例:store。因为有了 store ,容器组件才能在 mapStateToProps 中拿到 state 当前状态,在 mapDispatchToProps 中拿到 dispatch 方法。
React-Redux 提供了 <Provide>
组件,用 <Provide>
组件包裹住一个组件,然后在 <Provide>
组件中传入 store 值,那么在 <Provide>
包裹的组件及其其所有后代组件都可以直接使用到 store,从而也都能顺利拿到 state 和 dispatch 方法了。<Provide>
就是用于传递局部的全局变量,类似上下文 context
。
const store = createStore(reducer, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
源码链接
在了解了 React-Redux 的使用方法后,再重新做一遍 todo-list 项目,就会使得代码组织结构更加清晰了,代码结构如下: