最近在弄公司管理后台,考虑到只是在团队里面用,尝试采用React来写,状态采用Redux来管理,结合webpack开发。
在React中,所谓的组件就是一个个状态机器,当应用变得复杂时,状态如果管理不当,会使代码变得难以调试。Redux通过限制更新发生的时间和方式,尝试使state的变化变得可预测。Redux是一个很小的库,除了能与React一起使用,还支持其它库,如angular。
Redux可描述为如下三大原则:
- 单一数据源,整个应用只有一个store;
- state是只读的,只能通过触发action来修改;
- 通过编写reducers来描述action如何修改state。
接下来,通过Redux源码来了解Redux如何工作。
Redux仅有以下几个API,以Redux3.0.0为例
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
};
首先,来看一下createStore。简化后的源码如下:
export var ActionTypes = {
INIT: '@@redux/INIT'
};
export default function createStore(reducer, initialState) {
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
return currentState;
}
//订阅,返回取消订阅的接口
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
//触发action
function dispatch(action) {
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(listener => listener());
return action;
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
//第一次触发,用于初始化store树
dispatch({ type: ActionTypes.INIT });
return {
dispatch, //更新state
subscribe, //注册监听器
getState, //获取state
replaceReducer //更新reducer
};
}
createStore类似于发布订阅模式,主要用于连接action和reducers。reducer是纯函数,用于指明应用如何更新state。整个Redux应用只有一个单一的store,当需要拆分逻辑时,就需要组合reducer,那接下来就来看combineReducers。
import { ActionTypes } from '../createStore';
import isPlainObject from '../utils/isPlainObject';
import mapValues from '../utils/mapValues';
import pick from '../utils/pick';
export default function combineReducers(reducers) {
//过滤reducers,返回key-value object形式的reducers
var finalReducers = pick(reducers, (val) => typeof val === 'function');
//检测状态是否正确
Object.keys(finalReducers).forEach(key => {
var reducer = finalReducers[key];
//返回默认状态
if (typeof reducer(undefined, { type: ActionTypes.INIT }) === 'undefined') {
throw new Error();
}
var type = Math.random().toString(36).substring(7).split('').join('.');
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error();
}
});
//返回默认state,key对应的value为undefined
var defaultState = mapValues(finalReducers, () => undefined);
//返回一个组合函数,参数为state和action。
return function combination(state = defaultState, action) {
var finalState = mapValues(finalReducers, (reducer, key) => {
var newState = reducer(state[key], action);
if (typeof newState === 'undefined') {
throw new Error('');
}
return newState;
});
return finalState;
};
}
reducer在返回时不能修改旧的state,建议使用Object.assign()创建一个副本,且第一个参数为{},这样子就不会修改state。同时,在default情况下返回旧的state。
actions用于描述事情发生了这种事实,是store数据的唯一来源,通过store.dispatch()将action传递到store。action创建函数就是创建action的方法,只是纯函数,返回action对象而已。为了避免多次手动绑定action创建函数到dispatch()上,bindActionCreators用于自动把多个action创建函数绑定到dispatch()上。
import mapValues from '../utils/mapValues';
//将action创建函数绑定到dispatch上
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
//将多个action创建函数绑定到disptach上
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if (typeof actionCreators !== 'object' || actionCreators === null || actionCreators === undefined) { // eslint-disable-line no-eq-null
throw new Error('');
}
return mapValues(actionCreators, actionCreator =>
bindActionCreator(actionCreator, dispatch)
);
}
有了action、reducer、store这三样东西,我们就可以来理解Redux的单向数据流了。
根reducer将多个子reducer输出合成一个单一的state树 => store调用传入的reducer函数 => store保存了根reducer返回的完整state树。
当调用store.dispatch(action)时,根据action.type执行reducer并返回state,所有的监听器都将被调用。
接下来,看一下applyMiddleware接口,applyMiddleware接口用于包装dispatch()来达到我们想要的目的,比如打日志、异步请求等。
compose函数用于从右向左组合函数。
那么,理解了Redux中的API以及数据流向,再使用react-redux去进行开发,对react中state树的管理便会清晰很多。