用redux管理数据状态
动机
应用模块之间需要访问共享数据,采用redux管理数据状态。所有数据保存在store tree中,用于维护数据状态。
“Redux 是 JavaScript 状态容器,提供可预测化的状态管理。”
目录
三大原则
combineReducers
createStore
store方法
数据流
middlewares
实现redo&undo
三大原则
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的
惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。通过 store.dispatch() 将 action 传到 store。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作
let action = {
type: 'CHANGE_TEXT',
text:'helloworld'
};
store.dispatch(action);
可以通过Action 创建函数 生成 action 。
function changeText(text) {
return {
type: 'CHANGE_TEXT',
text
}
}
store.dispatch(changeText('helloworld'));
使用纯函数来执行修改
通过reducer改变state tree,要求reducer是纯函数,即只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
function todoApp(state = initialState, action) {
switch (action.type) {
case 'CHANGE_TEXT':
return Object.assign({}, state, {
text: action.text
})
default:
return state
}
}
注意:
1)不要修改 state。
2)在 default 情况下返回旧的 state。
combineReducers
开发一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
reducers.js
import { combineReducers } from 'redux'
const initialUser = {
name:"chen",
age:10
};
const initialJob = {
position:"engineer"
};
const userReducer = (state = initialUser, action) => {
let payload = action.payload;
let type = action.type;
switch (type) {
case "CHANGE_NAME":
state = Object.assign({},state,{ name : payload});
break;
case "ADD_AGE":
state = Object.assign({},state,{ age : state.age+1});
break;
default:
break;
}
return state;
};
const jobReducer = (state = initialJob, action) => {
let payload = action.payload;
let type = action.type;
switch (type) {
case "CHANGE_POSITION":
state = Object.assign({},state,{ position : payload});
break;
default:
break;
}
return state;
};
const reducers = combineReducers({
userReducer,
jobReducer
})
export default reducers
createStore
通过createStore生成store tree。createStore() 的第二个参数是可选的, 用于设置 state 初始状态
const store = createStore(
reducers
);
store方法
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。
数据流
严格的单向数据流
数据流动方向
问题:无法进行异步或辅助操作。
middlewares
提供了位于 action 被发起之后,到达 reducer 之前的扩展点。 可以用来进行日志记录、创建崩溃报告、调用异步接口或者路由。
以下是3个不同功能的中间件,通过输出数组的方式将3个中间件输出模块
middleware 机制
redux 提供了 applyMiddleware 这个 api 来加载 middleware
applyMiddleware.js源码
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
middlewares
const m1 = store => next => action => {
let startState = store.getState();
console.log("m1 start");
console.log(startState.user.age);
next(action);
let endState = store.getState();
console.log("m1 end");
console.log(endState.user.age);
};
const m2 = store => next => action => {
let startState = store.getState();
console.log("m2 start");
console.log(startState.user.age);
next(action);
let endState = store.getState();
console.log("m2 end");
console.log(endState.user.age);
};
const m3 = store => next => action => {
let startState = store.getState();
console.log("m3 start");
console.log(startState.user.age);
next(action);
let endState = store.getState();
console.log("m3 end");
console.log(endState.user.age);
};
const middlewares = [m1, m2, m3];
export default middlewares
index.js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import middlewares from './middlewares';
import reducers from './reducers';
import middlewares from './middlewares';
const defaultState = {};
const store = applyMiddleware(...middlewares)(createStore)(reducers, defaultState);
window.store = store;
applyMiddleware 是一个多层柯里化(curry)的函数
通过applyMiddleware(...middlewares)可以将[m1,m2,m3]3个中间件串联起来,下面分析以下具体实现方式。
1)初始化store将dispatch指向store.dispatch
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
2)用middlewareAPI封装store方法,middlewareAPI.dispatch方法最终指向store.dispatch。
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
3)通过将middlewareAPI传入每个中间件[m1,m2,m3],返回chain,此时chain=[f1,f2,f3],且由于闭包,f1,f2,f3中的store都指向middlewareAPI ,最终指向store。这样的好处就是每个f都能访问到同一个store。
chain = middlewares.map(middleware => middleware(middlewareAPI))
4)通过compose函数将[f1,f2,f3] 串联执行,最后dispatch被改写成了如下函数。
dispatch = f1(f2(f3(store.dispatch)));
/*
dispatch = (action)=>{
console.log("m1 start");
((action) =>{
console.log("m2 start");
((action) => {
console.log("m3 start");
store.dispatch(action)
console.log("m3 end");
})(action);
console.log("m2 end");
})(action);
console.log("m1 end");
}
*/
可以看到,此时f1’,f2’,f3’ 中的next指向中,只有最后一个f3’是指向store.dispatch,其余next指向前一个f'的输出。f1’,f2’,f3’所有store都指向middlewareAPI,最终getState和dispatch还是指向store。通过这样的方式可以将中间件串联起来。
例子
执行一次dispatch
store.dispatch({type:"ADD_AGE"})
输出结果如下:
注意
中间件的串联并不是简单依次执行,而是从middlewares数组的右边开始,依次将后一个中间件输出当成的函数(一个接收action的函数)作为前一个的next。
总结
最后,经过采用middleware,我们加入middleware来实现“非纯操作”,如请求异步接口,进队列,出队列,处理数据等。
加入middleware后的数据流
参考文献
https://zhuanlan.zhihu.com/p/...
https://github.com/reactjs/redux