从 Redux 到 React-Redux
在上篇文章中,我们用 React 结合 Redux 实现了一个小计算器,并将组件拆分为容器组件和木偶组件,本文再基于这个例子继续做一些拓展,并最终引出 React-Redux。
计算器应用的问题
对于计算器小应用,我们已经实现了其所有的功能,因此不再有功能上的问题了,那么还有没有可以优化的地方呢?当然。
在这个例子中,每个容器组件都会引入 store,这样会产生两个问题:
- 每个容器组件都去引入 store,有点麻烦
- 如果我们要写一个插件提供给别人使用,但是并不知道别人的 store 的引用路径,这就有点难办了
或许你会想:如果有个全局变量就好了,将 store 存放在全局变量中,就不用每次都引入了,也不用关心 store 的引用路径了。
那么可不可以提供一个最外层的组件,让其提供 store 对象,然后子组件直接去取用这个对象呢?当然可以。这里就需要用到我们前面说过的 context,可以在这篇文章中查看相应的内容。
context 回顾
context 是组件树上的一个全局属性,这里先回顾一下 context 的使用:
- 父级需要声明自己支持 context,并提供一个 getChildContext 函数返回初始 context
- 子组件需要声明自己需要使用 context
- 子组件取用 context 中的属性时,属性验证(PropTypes)必须和父组件中的声明保持一致
- 若子组件拥有构造函数(constructor),需要将 context 传入该构造函数,并在 super 调用父类构造函数时一并传入
- 可以使用 super(...args) 来代替 super(props,context)
定义 Provider 组件
下面,我们来定义一个 Provider 组件,用来在 context 中存放 store 对象,新建一个 Provider.js 文件:
import React,{ Component } from "react";
import PropTypes from "prop-types";
export default class Provider extends Component{
// 声明 context 属性的类型
static childContextTypes = {
store:PropTypes.object.isRequired,
}
// 返回初始的 context
getChildContext(){
return{
// store 对象由 Provider 组建的 props 传入
store:this.props.store,
}
}
render(){
// 直接渲染子节点
return this.props.children;
}
}
修改 App.js,让 Provider 作为组件树的根节点,并向其传入 store:
import React,{ Component } from "react";
import Counter from "./ContainerCounter";
import Provider from "./Provider";
import store from "./Store/store";
export default class App extends Component{
render(){
return(
<Provider store = { store }>
<Counter />
</Provider>
);
}
}
修改 ContainerCounter.js,从 context 中获取 store:
import React,{ Component } from "react";
import PropTypes from "prop-types";
import ACTIONS from "./Store/actions";
import UICounter from "./UICounter";
export default class ContainerCounter extends Component{
// 声明自己需要取用 context 属性
static contextTypes = {
store:PropTypes.object.isRequired,
}
constructor(props,context) {
super(props,context);
// 获取初始状态
this.state = {
// 使用 this.context.store 代替原先的 store
value:this.context.store.getState().value,
};
}
componentWillMount() {
// 监听 store 变化
this.context.store.subscribe(this.watchStore.bind(this));
}
componentWillUnmount() {
// 对 store 变化取消监听
this.context.store.unsubscribe(this.watchStore.bind(this));
}
// 监听回调函数,当 store 变化后执行
watchStore(){
// 回调函数中重新设置状态
this.setState(this.getCurrentState());
}
// 从 store 中获取状态
getCurrentState(){
return{
value:this.context.store.getState().value,
}
}
// 增加函数
increase(){
// 派发 INCREMENT Action
this.context.store.dispatch(ACTIONS.increament());
}
// 减少函数
decrease(){
// 派发 DECREAMENT Action
this.context.store.dispatch(ACTIONS.decreament());
}
render(){
return(
<UICounter
increase = { this.increase.bind(this)}
decrease = { this.decrease.bind(this)}
value = { this.state.value }
/>
);
}
}
效果和原先保持一致:
利用 context 获取 store.gif使用 React-Redux
通过前面的例子,我们对应用进行了如下改进:
- 将组件拆分为容器组件和木偶组件
- 使用 Provider 顶层组件,通过 context 提供 store
前面说到,Redux 作为一款状态管理工具,可以和任意的框架结合使用,当然,还有一个更加适用于 React 开发的 React-Redux 库。其所作的工作和我们前面的差不多,但增加了不少的易用性。使用 React-Redux 前需要先进行安装:
npm install --save react-redux
下面说一下 React-Redux 库中的两个概念:
- connect:用来连接木偶组件,并转换为容器组件,是的,我们不需要自己写容器组件了
- Provider 组件:顶层组件,通过 context 提供 store
connect 函数接受两个参数,这两个参数都是函数:
- mapStateToProps:从 store 中获取值,并传递给木偶组件
- mapDispatchToProps:定义回调函数,并将这些回调函数传递给木偶组件,木偶组件调用通过这些函数向 store 派发 action
下面看一下代码实现:
App.js:
import React,{ Component } from "react";
import Counter from "./ContainerCounter";
// 从 react-redux 中引入 Provider 组件
import { Provider } from "react-redux";
import store from "./Store/store";
export default class App extends Component{
render(){
return(
<Provider store = { store }>
<Counter />
</Provider>
);
}
}
ContainerCounter.js:
import { connect } from "react-redux";
import ACTIONS from "./Store/actions";
import UICounter from "./UICounter";
function mapStateToProps(state){
return{
value:state.value
}
}
function mapDispatchToProps(dispatch){
return{
// 增加
increase(){
// 派发 action
dispatch(ACTIONS.increament());
},
// 减少
decrease(){
dispatch(ACTIONS.decreament());
}
}
}
const ContainerCounter = connect(
mapStateToProps,
mapDispatchToProps,
)(UICounter);
export default ContainerCounter;
最终效果和前面一样。
可见,使用 react-redux 后,逻辑变得更加清晰了,也减少了许多操作,比如对 store 进行监听(subscribe),声明子组件的 context 等。这些事情 react-redux 都帮我们做了,我们只需定义两个函数(mapStateToProps,mapDispatchToProps)传递给 connect 函数,connect 函数调用的结果也是一个函数,我们再将木偶组件传入这个结果函数,就自动生成了容器组件。更易维护,代码量更少。
除此之外,react-redux 还进行了一些优化的处理,比如 shouldComponentUpdate 的优化等,但核心原理没有变,只是进行了一层封装处理。
因此,在使用 react-redux 之前,最好对 Redux 有一些了解,否则会因为不清楚内部的原理而成为代码搬运工。
总结
本文首先从全局 store 入手,构造了 Redux 版本的 Provider 组件,在此基础上引入了更加方便的 react-redux 库,更加适用于 React 开发,提升了开发效率和项目的可维护性。
完。