React框架本身只应用于View,如果基于MVC模式开发,还需要Model和Control层,这样催生了Flux的产生,而Redux是基于Flux理念的一种解决方式。
从《React入门系列》可知,组建直接传递参数或者事件都需要props一层层代理,对于复杂组件,它可能嵌套的子组件非常多,层级也比较深,那么,如果还采用props链条来维护组件通信或者数据共享,将非常困难,也不利于开发和维护。
Flux
Flux框架也是一种MVC框架,不同于传统的MVC,它采用单向数据流,不允许Model和Control互相引用。Flux框架大致如下(来自网络):
- Actions: 驱动Dispatcher发起改变
- Dispatcher: 负责分发动作(事件)
- Store: 储存数据,处理数据
- View: 视图部分
Dispatcher只会暴露一个函数-dispatch,接受Action为参数,发起动作。如果需要增加新功能,不需要改变或者增加接口,只需增加Action类型。Dispatch的初始化和使用如下:
// Dispatcher.js
import {Dispatcher} from 'flux';
export default new Dispatcher();
// actions
import AppDispatcher from './Dispatcher.js';
export const increment = (number) => {
AppDispatcher.dispatch({
type: 'ADD',
value: number
});
};
Store 一般会继承EventEmitter,实现事件监听,发布,卸载。需要将store注册到Dispatcher实例上才能够发挥作用。
Store可以直接修改对象,这点和Redux不同。
import AppDispatcher from './Dispatcher.js';
let value = 10;
const store = Object.assign({}, EventEmitter.prototype, {
getValue: function() {
return value;
}
emitChange: function() {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
store.dispatchToken = AppDispatcher.register((action) => {
if (action.type === 'ADD') {
value = value + action.value;
store.emitChange();
}
// your codes
});
export default store;
view组件中的state应该与Flux store保持一致,如下:
function MyView extend Component {
constructor(props){
this.state = { count: store.getValue()}
}
// 声明周期函数(组件加载和卸载),需要调用store的事件注册函数,
// 将处理组件state变化的函数设置为注册函数的回调方法
componentDidMount() {
store.addChangeListener(this.onChange);
}
componentWillUnmount() {
store.removeChangeListener(this.onChange);
}
onChange() {
const newCount = store.getValue();
this.setState({count: newCount});
}
// 组件的事件函数,需要调用Action触发状态更新
onClickIncrementButton() {
Actions.increment(text);
}
}
Flux的缺点为:
- 一个应用可以拥有多个store,多个store直接可能有依赖关系(相互引用);
- Store封装了数据和处理数据的逻辑。
针对Flux的不足,Redux框架出现。
Redux
相比Flux,Redux有如下两个特点:
- 在整个应用只提供一个Store,它是一个扁平的树形结构,一个节点状态应该只属于一个组件。
- 不允许修改数据。即不能修改老状态,只能返回一个新状态。
Redux数据流如下(来自网络):
不同于 Flux ,Redux 并没有 dispatcher 的概念(Store已经集成了dispatch方法,所以不需要Dispatcher)。它依赖纯函数来替代事件处理器,这个纯函数叫做Reducer。Reducer在Redux里是个很重要的概念,其封装了处理数据的逻辑。
在计算机编程中,假如满足下面这两个句子的约束,一个函数可能被描述为一个纯函数:
1. 给出同样的参数值,该函数总是求出同样的结果。
该函数结果值不依赖任何隐藏信息或程序执行处理可能改变的状态或在程序的两个不同的执行。
2. 结果的求值不会促使任何可语义上可观察的副作用或输出。
简单说,一个纯函数,只要输入相同,无论调用多少次,输出都是一样的。这就要求,绝不能修改输入参数,因为输入参数有可能在其他地方用到。下面是一个简单的Reducer对象:
export default (state, action) => {
switch (action.type) {
case 'INCREMENT':
return {...state, value: action.value + 1};
default:
return state;
}
}
把数据逻辑分离开后,Store就变得简单了。它的构造函数需要一个reducer对象(每个组件对应一个reducer,通过combineReducers
函数合并N个子reducer为一个主reducer),初始化数据,和中间件(可选)。由于Store已经集成了dispatch方法,所以不需要Dispatcher。一个简单的Store如下:
import {createStore, combineReducers} from 'redux';
const reducer = combineReducers({
reducer: incrementReducer
});
export default createStore(reducer, {});
View层通过store.dispatch
触发动作:
onIncrement() {
store.dispatch(Actions.increment(value));
}
组件 Context
看到这里,可以发现Flux和Redux都需要显性的在View里面引入store, import store from './Store'
。如果可以在一个应用中,只引入一次store,然后所有组件都可以访问到,那该多好?!非常幸运,React提供了这样的功能,即Context。
Context就是“上下文环境”,让一个数状组件上所有组件都能访问一个共有的对象。
让顶层容器组件支持Context,那么子组件都可以访问到store,无需各自import。可以如下定义一个顶层组件:
import {PropTypes, Component} from 'react';
class Provider extends Component {
//必须实现getChildContext方法
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.propTypes = {
store: PropTypes.object.isRequired
}
// 必须定义静态属性childContextTypes ,和getChildContext()对应
Provider.childContextTypes = {
store: PropTypes.object
};
export default Provider;
在入口文件内使用顶层组件:
import React from 'react';
import ReactDOM from 'react-dom';
import store from './Store.js';
import Provider from './Provider.js';
ReactDOM.render(
<Provider store={store}>
...
</Provider>,
document.getElementById('root')
);
所有子组件对象都可直接访问到store对象:
const value = this.context.store.getState();
react-redux
要声明一点,Redux并不是专为React开发的,它可以应用在任何框架上。针对React工程,可以使用react-redux库帮助我们更快,更便捷得搭建Redux工程,让代码更加精简。react-redux库提供了如下功能:
- 把组件拆分为容器组件和傻瓜组件,使用者只需要写傻瓜组件;
- 使用React的Context提供了一个所有组件都可以直接访问的Context,即react-redux Provider;
于是,我们不需要自己写顶层组件了,只要导入react-redux的Provider,如下:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import store from './Store.js';
ReactDOM.render(
<Provider store={store}>
...
</Provider>,
document.getElementById('root')
);
Action和Store写法不变,与Redux相同。
组件变得更加简洁,如下:
function Counter({caption, onIncrement, onDecrement, value}) {
return (
<div>
<button style={buttonStyle} onClick={onIncrement}>+</button>
<span>{caption} count: {value}</span>
</div>
);
}
//store中状态state到傻瓜组件属性props的映射
function mapStateToProps(state, ownProps) {
return {
value: state[ownProps.caption]
}
}
//傻瓜组件中用户的每个动作,都转换为派送给store的动作
function mapDispatchToProps(dispatch, ownProps) {
return {
onIncrement: () => {
dispatch(Actions.increment(ownProps.caption));
}
}
}
// connent函数:连接容器组件和傻瓜组件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
可以看到,用了react-redux之后,代码精简不少,而且逻辑更加清晰。
小结
从Flux到Redux,再到react-redux,从这个简短历程中,我们可以看到框架设计上的演进,而redux + react-redux也是React开发万家桶的标配。到了这里,可以忘记Flux啦~