Redux 学习笔记 - 源码阅读
很久之前就看过一遍 注意:本文不是单纯的讲 整体架构在我看来,Redux 核心理念很简单
代码量也不大,源码结构很简单: .src |- utils |- applyMiddleware.js |- bindActionCreators.js |- combineReducers.js |- compose.js |- createStore.js |- index.js 其中 index.js这是入口函数,主要是为了暴露 这里有这么一段代码,主要是为了校验非生产环境下是否使用的是未压缩的代码,压缩之后,因为函数名会变化, if ( process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed' ) { warning(...) )} createStore这个函数是 // 初始化的 action export const ActionTypes = { INIT: '@@redux/INIT' } export default function createStore(reducer,preloadedState,enhancer) { // 首先进行各种参数获取和类型校验,不具体展开了 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') {...} if (typeof reducer !== 'function') {...} //各种初始化 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false // 保存一份 nextListeners 快照,后续会讲到它的目的 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } function getState(){...} function subscribe(){...} function dispatch(){...} function replaceReducer(){...} function observable(){...} // 初始化 dispatch({ type: ActionTypes.INIT }) return { dispatch,subscribe,getState,replaceReducer,[$$observable]: observable } } 下面我们具体来说 ActionTypes这里的 ensureCanMutateNextListeners它的目的主要是保存一份快照,下面我们就讲讲 subscribe目的是为了添加一个监听函数,当 function subscribe(listener) { // 异常处理 ... // 标记是否有listener let isSubscribed = true // subscribe时保存一份快照 ensureCanMutateNextListeners() nextListeners.push(listener) // 返回一个 unsubscribe 函数 return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false // unsubscribe 时再保存一份快照 ensureCanMutateNextListeners() //移除对应的 listener const index = nextListeners.indexOf(listener) nextListeners.splice(index,1) } } 这里我们看到了 store.subscribe(function(){ console.log('first'); store.subscribe(function(){ console.log('second'); }) }) store.subscribe(function(){ console.log('third'); }) dispatch(actionA) 这时候的输出就会是 first third 在后续的 const listeners = currentListeners = nextListeners 它的目的则是确保每次 dispatch
function dispatch(action) { // 这里两段都是异常处理,具体代码不贴了 if (!isPlainObject(action)) { ... } if (typeof action.type === 'undefined') { ... } // 立一个标志位,reducer 内部不允许再dispatch actions,否则抛出异常 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } // 捕获前一个错误,但是会将 isDispatching 置为 false,避免影响后续的 action 执行 try { isDispatching = true currentState = currentReducer(currentState,action) } finally { isDispatching = false } // 这就是前面说的 dispatch 时会获取最新的快照 const listeners = currentListeners = nextListeners // 执行当前所有的 listeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } 这里有两点说一下我的看法:
const listener = listeners[i] listener() 乍一看觉得会为什么不直接 getState获取当前的 replaceReducer更换当前的 function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } // 更换 reducer currentReducer = nextReducer // 这里会进行一次初始化 dispatch({ type: ActionTypes.INIT }) } observable主要是为 combineReducers先问个问题:为什么要提供一个 我先贴一个正常的 function reducer(state,action){ switch (action.type) { case ACTION_LIST: ... case ACTION_BOOKING: ... } } 当代码量很小时可能发现不了问题,但是随着我们的业务代码越来越多,我们有了列表页,详情页,填单页等等,你可能需要处理 源码核心部分如下: export default function combineReducers(reducers) { // 各种异常处理和数据清洗 ... return function combination(state = {},action) { const finalReducers = {}; // 又是各种异常处理,finalReducers 是一个合法的 reducers map ... let hasChanged = false; const nextState = {}; for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i]; const reducer = finalReducers[key]; // 获取前一次reducer const previousStateForKey = state[key]; // 获取当前reducer const nextStateForKey = reducer(previousStateForKey,action); nextState[key] = nextStateForKey; // 判断是否改变 hasChanged = hasChanged || nextStateForKey !== previousStateForKey; } // 如果没改变,返回前一个state,否则返回新的state return hasChanged ? nextState : state; } } 注意这一句,每次都会拿新生成的 hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 随着业务量的增大,我们就可以利用嵌套的 compose接受一组函数,会从右至左组合成一个新的函数,比如 核心就是这么一句 return funcs.reduce((a,b) => (...args) => a(b(...args))) 拿一个例子简单解析一下 [f1,f3].reduce((a,b) => (...args) => a(b(...args))) step1: 因为 reduce 没有默认值,reduce的第一个参数就是 f1,第二个参数是 f2,因此第一个循环返回的就是 (...args)=>f1(f2(...args)),这里我们先用compose1 来代表它 step2: 传入的第一个参数是前一次的返回值 compose1,第二个参数是 f3,可以得到此次的返回是 (...args)=>compose1(f3(...args)),即 (...args)=>f1(f2(f3(...args))) bindActionCreator简单说一下 一般我们会这么调用 dispatch({type:"Action",value:1}) 但是为了保证 function actionCreatorTest(value){ return { type:"Action",value } } //调用时 dispatch(actionCreatorTest(1)) 再进一步,我们每次调用 核心代码就是这么一段: function bindActionCreator(actionCreator,dispatch) { return (...args) => dispatch(actionCreator(...args)) } 下面的代码主要是对 export default function bindActionCreators(actionCreators,dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators,dispatch) } //类型错误 if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( ... ) } // 处理多个actionCreators var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator,dispatch) } } return boundActionCreators } applyMiddleware想一下这种场景,比如说你要对每次
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer,enhancer) => { const store = createStore(reducer,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 } } } 可以看到 之后我们执行了 chain = middlewares.map(middleware => middleware(middlewareAPI)) 生成了一个 再往后就是 dispatch = compose(...chain)(store.dispatch)
最后,我们会用新生成的 但是,在 function createThunkMiddleware(extraArgument) { return ({ dispatch,getState }) => next => action => { if (typeof action === 'function') { return action(dispatch,extraArgument); } // 用next而不是dispatch,保证可以进入下一个中间件 return next(action); }; } 这里有三层函数
到这里,整个中间件的逻辑就很清楚了,这里还有一个点要注意,就是在中间件的内部, 那么这里的 小结结合这一段时间的学习,读了第二篇源码依然会有收获,比如它利用函数式和 那么, (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |