You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constreducerKeys=Object.keys(reducers)constfinalReducers={}for(leti=0;i<reducerKeys.length;i++){constkey=reducerKeys[i]if(process.env.NODE_ENV!=='production'){if(typeofreducers[key]==='undefined'){warning(`No reducer provided for key "${key}"`)}}if(typeofreducers[key]==='function'){finalReducers[key]=reducers[key]}}constfinalReducerKeys=Object.keys(finalReducers)// This is used to make sure we don't warn about the same// keys multiple times.letunexpectedKeyCacheif(process.env.NODE_ENV!=='production'){unexpectedKeyCache={}}letshapeAssertionErrortry{assertReducerShape(finalReducers)}catch(e){shapeAssertionError=e}
lethasChanged=falseconstnextState={}for(leti=0;i<finalReducerKeys.length;i++){constkey=finalReducerKeys[i]constreducer=finalReducers[key]constpreviousStateForKey=state[key]constnextStateForKey=reducer(previousStateForKey,action)if(typeofnextStateForKey==='undefined'){consterrorMessage=getUndefinedStateErrorMessage(key,action)thrownewError(errorMessage)}nextState[key]=nextStateForKeyhasChanged=hasChanged||nextStateForKey!==previousStateForKey}hasChanged=hasChanged||finalReducerKeys.length!==Object.keys(state).lengthreturnhasChanged ? nextState : state
if(typeofenhancer!=='undefined'){if(typeofenhancer!=='function'){thrownewError('Expected the enhancer to be a function.')}returnenhancer(createStore)(reducer,preloadedState)}
(...args)=>{conststore=createStore(...args)letdispatch=()=>{thrownewError('Dispatching while constructing your middleware is not allowed. '+'Other middleware would not be applied to this dispatch.')}constmiddlewareAPI={getState: store.getState,dispatch: (...args)=>dispatch(...args)}constchain=middlewares.map(middleware=>middleware(middlewareAPI))dispatch=compose(...chain)(store.dispatch)return{
...store,
dispatch
}}
For example, compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).
这就是为什么像logger这样的中间件需要注意顺序的原因了,如果放在最后一个参数。最后一个中间件可以拿到最终的store.dispatch,所有能在它的前后记录变更,不受其他影响。nodejs的koa框架的洋葱模型与之类似。
当一个应用足够大的时候,我们使用一个
reducer
函数来维护state
是会碰到麻烦的,太过庞大,分支会很多,想想都会恐怖。基于以上这一点,redux
支持拆分reducer
,每个独立的reducer
管理state
树的某一块。combineReducers 函数
根据
redux
文档介绍,来看一下这个函数的实现。先看一下函数的结构,就如文档所说,传入一个
key-value
对象,value
为拆分的各个reducer
,然后返回一个reducer
函数,就如代码里面的combination
函数,看入参就知道和reducer
函数一致。检查传入的 reducers 对象的合理性
检查的操作就是在返回之前,看看代码。
Object.keys
拿到入参对象的key
,然后声明一个finalReducers
变量用来存方最终的reducer
。reducerKeys
,检查每个reducer
的正确性,比如控制的判断,是否为函数的判断,如果符合规范就放到finalReducerKeys
对象中。Object.keys
获取清洗后的key
assertReducerShape(finalReducers)
函数去检查每个reducer
的预期返回值,它应该符合以下:combination 函数
经过了检查,最终返回了
reducer
函数,相比我们直接写reducer
函数,这里面预置了一些操作,重点就是来协调各个reducer
的返回值。如果之前检查有警告或者错误,在执行
reducer
的时候就直接抛出。最后在调用
dispatch
函数之后,处理state
的代码如下:isChanged
来表示,经过reducer
处理之后,state
是否变更了。finalReducerKeys
。reducer
和对应的key
并且根据key
获取到state
相关的子树。reducer(previousStateForKey, action)
获取对应的返回值。undefined
,然后进行相应的报错。key
中。===
进行比较新获取的值和state
里面的旧值,可以看到这里只是比较了引用,注意redcuer
里面约束有修改都是返回一个新的state
,所有如果你直接修改旧state
引用的话,这里的hasChanged
就会被判断为false
,在下一步中,如果为false
就会返回旧的state
,数据就不会变化了。hasChanged
判断返回原始值还是新值。添加中件件
当我们需要使用异步处理
state
的时候,由于reducer
必须要是纯函数,这和redux
的设计理念有关,为了可以能追踪到每次state
的变化,reducer
的每次返回值必须是确定的,才能追踪到。具体放在后面讲。当使用中间件,我们需要通过
applyMiddleware
去整合中间件,然后传入到createStore
函数中,这时候相应的流程会发生变化。先看看
createStore
函数对这部分的处理。这里的
enhancer
就是applyMiddleware(thunk, logger, ...)
执行后的返回值。可以看到,enhancer
函数执行,需要把createStore
函数传入,说明enhancer
内部会自己去处理一些其他操作后,再回来调用createStore
生成store
。applyMiddleware 函数
首先看一下
applyMiddleware
的结构。可以看到
applyMiddleware
函数啥都没干,只是对传入的middlewares
参数形成了一个闭包,把这个变量缓存起来了。确实很函数式。接下来看一下它的返回的这个函数:
它返回的这个函数也只是把
createStore
缓存(柯里化绑定)了下来,目前在createStore
执行到了这一步enhancer(createStore)
。再看看这个返回函数执行后的返回函数。
createStore
传入reducer, preloadedState
这两个参数,也就是...args
,生成store
。dispatch
为一个只会抛错误的空函数。middlewareAPI
变量,对象里面有两个属性,分别为getState
和dispatch
,这里的dispatch
是一个函数,执行的时候会调用当前作用域的dispatch
变量,可以看到,在这一步dispatch
还是那个空函数。middlewares
,将构建的middlewareAPI
变量传入,生成一个新的队列,里面装的都是各个中间件执行后的返回值(一般为函数)。compose
去生成新的dispatch
函数。store
的所有属性返回,然后使用新生成的dispatch
去替换默认的dispatch
函数。compose 函数
中间件的重点就是将
dispatch
替换成了新生成的dispatch
函数,以至于可以在最后调用store.dispatch
之前做一些其他的操作。生成的核心在于compose
函数,接下来看看。reduce
方法,对这些参数(函数),进行一种整合。看看官方注释:再回到
applyMiddleware
函数,经过compose
函数处理后,最后返回了一个函数。再把
store.dispatch
传入到这些整合后的中间件后,得到最后的dispatch
函数。redux-thunk 中间件
看了
redux
是怎么处理整合中间件的,看一下redux-thunk
的实现,加深一下印象。可以看到最终导出的是
createThunkMiddleware
函数的返回值,这就是中间件的一个实现了。store
,也就是applyMiddleware
函数在执行const chain = middlewares.map(middleware => middleware(middlewareAPI))
会传入的。compose(...chain)(store.dispatch)
函数得到的,这里会将其他的中间件作为参数next
传入。可以看到,当传入的
action
为函数的时候,直接就return
了,打断了中间件的pie
执行,而是去执行了action
函数里面的一些异步操作,最后异步成功或者失败了,又重新调用dispatch
,重新启动中间件的pie
。尾巴
上面说到,为什么
reducer
为什么一定需要是纯函数?下面说说个人理解。通过源码,可以反应出来。
hasChanged = hasChanged || nextStateForKey !== previousStateForKey ... return hasChanged ? nextState : state
。从这一点可以看到,是否变化
redux
只是简单的使用了精确等于来判断的,如果reducer
是直接修改旧值,那么这里的判断会将修改后的丢弃掉了。那么为什么redux
要这么设计呢?我在网上查了一些文章,说的最多的就是说,如果想要判断A、B
两对象是否相对,就只能深度对比每一个属性,我们知道redux
应用在大型项目上,state
的结构会很庞大,变更频率也是很高的,每次都进行深度比较,消耗很大。所有redux
就把这个问题给抛给开发者了。还有为什么
reducer
或者vuex
里面的mutation
中,不能执行异步操作,引用·vuex
官方文档:Mutation 必须是同步函数
一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
文档地址,
reducer
也是同理。小结
几行代码可以做很多事情,比如中间件的串联实现,函数式的编程令人眼花缭乱。
分析了
combineReducer
和applyMiddleware
,redux
也就梳理完了。中间件的编程思想很值得借鉴,在中间件上下相互不知的情况下,也能很好的协作。参考文章
The text was updated successfully, but these errors were encountered: