一 redux 思想
首先,每一个webApp有且只有一个state tree,为方便管理和跟踪state的变化,也为了减少混乱,redux只允许通过发送(dispatch)action的方式来改变state。旧state在action的作用下产生新state,这个过程叫做reduce,具体逻辑由用户自定义,称为reducer函数。
另外,redux允许在dispatch action与action到达reducer之间拦截action,做更多的动作,即中间件(middleware),这就像插件机制一样,允许多样的扩展,其中一类重要中间件的功能就是处理异步(与服务端通信)。
二、redux使用
redux用起来是这样的:用户先写好处理state各个子部分的多个reducers,通过redux提供的combineReducers函数合并成一个rootReducer;然后选好需要的中间件;reducer和中间件作为参数,通过createStore函数创建Store对象。Store是redux运用起来的核心对象,通过store可以获取state--store.getState(), dispatch action以及订阅state变化store.subscribe()。
1 const rootReducer = combineReducers(reducers); 2 const store = createStore(rootReducer, applyMiddleware(...middlewares)) 3 4 const state0 = store.getState(); 5 6 const action0 = actionCreator('do something'); 7 store.dispatch(action0); 8 9 const unSubscribeHandler = store.subscribe(callback);
三、redux代码解析
参考文章(2)以及源代码(3),实现redux核心逻辑的代码如下(自己掰的):
1 const createStore = (reducer, enhancer) => { 2 if (typeof enhancer === 'function') { 3 return enhancer(createStore)(reducer); 4 } 5 6 let currentState = undefined; 7 let currentReducer = reducer; 8 let subscribers = []; 9 10 function dispatch(action) { 11 if (typeof currentReducer === 'function') { 12 currentState = currentReducer(currentState, action); 13 subscribers.forEach(sfn => sfn()); 14 } 15 return action; 16 } 17 18 function getState() { 19 return currentState; 20 } 21 22 function unSubscribe(fn) { 23 subscribers = subscribers.filter(sfn => sfn === fn); 24 } 25 26 function subscribe(fn) { 27 if (typeof fn !== 'function') { 28 throw new Error('subscriber must be a function!'); 29 } 30 const oldFn = subscribers.find(fnx => fnx === fn); 31 if (!oldFn) { 32 subscribers.push(fn); 33 } 34 return () => unSubscribe(fn); 35 } 36 37 dispatch({ type: 'init' }); 38 return { dispatch, getState, subscribe }; 39 }; 40 41 // combine multiple reducers into one. 42 // const rootReducer = combineReducers(reducerX, reducerY, reducerZ); 43 // const store = createStore(rootReducer); 44 const combineReducers = reducers => (state = {}, action) => { 45 const currentState = state; 46 reducers.forEach((reducer) => { 47 const partialStateName = reducer.name; 48 currentState[partialStateName] = reducer(currentState[partialStateName], action); 49 }); 50 return currentState; 51 }; 52 53 // const actionA = ActionCreators.doA('xxx'); 54 // dispatch(actionA); 55 // const actionB = ActionCreators.doB('yyy'); 56 // dispatch(actionB); 57 // --> 58 // const Action = bindActionCreators(ActionCreators, dispatch); 59 // Action.doA('xxx'); 60 // Action.doB('yyy'); 61 const bindActionCreators = (actions, dispatch) => { 62 const newActions = {}; 63 for (const key of Object.getOwnPropertyNames(actions)) { 64 newActions[key] = args => dispatch(actions[key](args)); 65 } 66 return newActions; 67 }; 68 69 // funcs = [fa, fb, fc] 70 // compose(...funcs)(...args) <=> fa(fb(fc(...args))) 71 const compose = (...funcs) => { 72 return funcs.reduce((a, b) => (...args) => a(b(...args))); 73 }; 74 75 // 返回一个enhancer: enhancer(createStore)(reducer) 76 const applyMiddleware = (...middlewares) => { 77 return createStore => reducer => { 78 const store = createStore(reducer); 79 let dispatch = store.dispatch; 80 //包装dispatch 81 const middlewareAPI = { 82 getState: store.getState, 83 dispatch: action => dispatch(action) 84 }; 85 const chain = middlewares.map(middleware => middleware(middlewareAPI)); 86 const enhancedDispatch = compose(...chain)(dispatch); 87 return { ...store, dispatch: enhancedDispatch }; 88 }; 89 }; 90 91 const logger = ({ getState, dispatch }) => next => action => { 92 console.log('logger: before action dispatch: ', getState()); 93 console.log('logger: action: ', action); 94 const result = next(action); 95 console.log('logger: after action dispatch: ', getState()); 96 return result; 97 }; 98 99 const logger1 = ({ getState, dispatch }) => next => action => { 100 console.log('logger1: before action dispatch: ', getState()); 101 console.log('logger1: action: ', action); 102 const result = next(action); 103 console.log('logger1: after action dispatch: ', getState()); 104 return result; 105 };
主要实现这几个函数:
createStore(reducer, enhancer); 以及store.dispatch() store.getState()
combineReducers(reducers);
applyMiddleware(...middlewares); 以及 compose(...funcs);
(1) createStore(reducer, enhancer)
关注enhancer这个参数,enhancer是中间件经过applyMiddleware之后的函数,其实是参数版的装饰器。
enhancer作用在createStore函数上,就是在原来的创建store之外还做了些事情,具体就是改造了store的dispatch函数,加入了中间件。
参考以下代码:比较装饰器decorator直接作为装饰器使用以及作为参数的装饰器。
1 function funcA(x, enhancer) { 2 if (typeof enhancer === 'function') { 3 return enhancer(funcA)(x); 4 } 5 6 console.log('in funcA'); 7 return 2 * x; 8 } 9 10 const decorator = (fn) => (x) => { 11 console.log('before'); 12 let result = fn(x); 13 console.log('after'); 14 return result * 10; 15 }; 16 17 18 // commonly used decorator 19 console.log(decorator(funcA)(5)); 20 console.log('==========='); 21 // decorator as argument 22 console.log(funcA(5, decorator));
(2)compose(...funcs)
这个函数的作用是把多个函数(funcs)连成一个函数,其效果等同于逆序地逐个把函数作用于参数上:
compose(fa, fb, fc)(x) 等价于 fa(fb(fc(x)))
这里的技巧是使用reduce函数:通常的reduce是数据与数据之间reduce,这里用在了函数与函数之间,
注意reduce里面的函数的返回值也是函数,体味一下。
const compose = (...funcs) => { return funcs.reduce((a, b) => (...args) => a(b(...args))); };
(3)applyMiddleware(...middlewares)
applyMiddleware(...middlewares)的结果是产生createStore函数的装饰器enhancer。
具体的实现是先创建原生的store,然后增强dispatch函数。
1 const applyMiddleware = (...middlewares) => { 2 return createStore => reducer => { 3 const store = createStore(reducer); 4 let dispatch = store.dispatch; 5 //包装dispatch 6 const middlewareAPI = { 7 getState: store.getState, 8 dispatch: action => dispatch(action) 9 }; 10 const chain = middlewares.map(middleware => middleware(middlewareAPI)); 11 const enhancedDispatch = compose(...chain)(dispatch); 12 return { ...store, dispatch: enhancedDispatch }; 13 }; 14 };
中间件的实际形式是生成器(creator),它接收一个包含dispatch和getState方法的对象,
返回next函数的装饰器(decorator):
someMiddleware = ({ getState, dispatch }) => next => action => {};
这里面的next,就是下一个next函数的装饰器,前一层装饰后一层,层层装饰直到最后一个next,就是原生的dispatch函数本身了。具体例子参考代码中的logger中间件。
上面代码中chain中的元素就都是装饰器了,然后经过compose,再作用到dispatch上就产生了增强版的dispatch,
用这个enhancedDispatch替换原生store中的dispatch就大功告成了。
尾声
为了验证上述逻辑理解的是否正确,加几个middleware玩玩。
const interceptor = ({ getState, dispatch }) => next => action => { if (action.type === 'DECREMENT') { console.log(`action: ${action.type} intercepted!`); return null; } else { return next(action); } }; const actionModifier = ({ getState, dispatch }) => next => action => { if (action.type === 'DECREMENT') { console.log(`action: ${action.type} got and modified to type = INCREMENT!`); action.type = 'INCREMENT'; } return next(action); }; const useNativeDispatch = ({ getState, dispatch }) => next => action => { if (action.type === 'DECREMENT') { console.log('shortcut to native dispatch!'); return dispatch(action); } else { return next(action); } };
interceptor 会拦截特定类型的action,然后不再向后传播,结果就是之前的中间件还能执行,后面的中间件就不执行了;
actionModifier 会修改特定类型action的数据,再向后传播,所以前后中间件会看到不同的action;
useNativeDispatch 在遇到特定类型的action时,会跳过后面所有中间件,直接调用原生dispatch。
完毕!
参考文章:
(1)redux官方basic&advanced tutorial
(3)redux github