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
{{ message }}
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.
// 传递两个参数时,实际传递的是 reducer 和 enhancer,preloadedState 为 undefinedif(typeofpreloadedState==='function'&&typeofenhancer==='undefined'){enhancer=preloadedStatepreloadedState=undefined}// 传递三个参数时,传递的是 reducer preloadedState enhancer(enhancer必须为函数)if(typeofenhancer!=='undefined'){if(typeofenhancer!=='function'){thrownewError('Expected the enhancer to be a function.')}// 如果传入了 enhancer(一个组合 store creator 的高阶函数)则控制反转,交由enhancer来加强要生成的store// 再对这个加强后的 store 传递 reducer 和 preloadedStatereturnenhancer(createStore)(reducer,preloadedState)}// 传入的reducer必须是一个纯函数,且是必填参数if(typeofreducer!=='function'){thrownewError('Expected the reducer to be a function.')}
functionsubscribe(listener){// 传入的listener必须是一个可以调用的函数,否则报错if(typeoflistener!=='function'){thrownewError('Expected listener to be a function.')}// 同上,保证纯函数不带来副作用if(isDispatching){thrownewError('...')}letisSubscribed=true// 在每次subscribe的时候,nextListenerx先拷贝currentListeners,再push新的listenerensureCanMutateNextListeners()nextListeners.push(listener)returnfunctionunsubscribe(){if(!isSubscribed){return}// 同上,保证纯函数不带来副作用if(isDispatching){thrownewError('...')}isSubscribed=false// 在每次unsubscribe的时候,深拷贝一次currentListeners,再对nextListeners取消订阅当前listenerensureCanMutateNextListeners()// 从nextListeners中去掉unsubscribe的listenerconstindex=nextListeners.indexOf(listener)nextListeners.splice(index,1)}}
functiondispatch(action){// action必须是一个plain object,如果想要能处理传进来的函数的话必须使用中间件(redux-thunk等)if(!isPlainObject(action)){thrownewError('Actions must be plain objects. '+'Use custom middleware for async actions.')}// action必须定义type属性if(typeofaction.type==='undefined'){thrownewError('Actions may not have an undefined "type" property. '+'Have you misspelled a constant?')}// 同上,保证纯函数不带来副作用if(isDispatching){thrownewError('Reducers may not dispatch actions.')}// currentReducer不可预料是否会报错,所以try,但不catchtry{isDispatching=truecurrentState=currentReducer(currentState,action)}finally{// 必须在结束的时候将isDispatching归位isDispatching=false}// 在这里体现了currentListeners和nextListeners的作用constlisteners=(currentListeners=nextListeners)// 这里使用for而不是forEach,是因为listeners是我们自己创造的,不存在稀疏组的情况,所有直接用for性能来得更好// 见 https://github.com/reactjs/redux/commit/5b586080b43ca233f78d56cbadf706c933fefd19// 附上Dan的原话:This is an optimization because forEach() has more complicated logic per spec to deal with sparse arrays. Also it's better to not allocate a function when we can easily avoid that.// 这里没有缓存listeners.length,Dan相信V8足够智能会自动缓存,相比手工缓存性能更好for(leti=0;i<listeners.length;i++){// 这里将listener单独新建一个变量而不是listener[i]()// 是因为直接listeners[i]()会把listeners作为this泄漏,而赋值为listener()后this指向全局变量// https://github.com/reactjs/redux/commit/8e82c15f1288a0a5c5c886ffd87e7e73dc0103e1constlistener=listeners[i]listener()}returnaction}
接下来看getState,就是一个return
functiongetState(){// 参考:https://github.com/reactjs/redux/issues/1568// 为了保持reducer的pure,禁止在reducer中调用getState// 纯函数reducer要求根据一定的输入即能得到确定的输出,所以禁止了getState,subscribe,unsubscribe和dispatch等会带来副作用的行为if(isDispatching){thrownewError('You may not call store.getState() while the reducer is executing. '+'The reducer has already received the state as an argument. '+'Pass it down from the top reducer instead of reading it from the store.')}returncurrentState}
functionreplaceReducer(nextReducer){if(typeofnextReducer!=='function'){thrownewError('Expected the nextReducer to be a function.')}currentReducer=nextReducer// ActionTypes.REPLACE其实就是ActionTypes.INIT// 重新INIT依次是为了获取新的reducer中的默认参数dispatch({type: ActionTypes.REPLACE})}
说明
本文所分析的Redux版本为3.7.2
分析直接写在了注释里,放在了GitHub上 —> 仓库地址
分析代码时通过查看Github blame,参考了Redux的issue及PR来分析各个函数的意图而不仅是从代码层面分析函数的作用,并且分析了很多细节层面上写法的原因,比如:
dispatch: (...args) => dispatch(…args)
为什么不只传递一个action
?listener
的调用为什么从forEach
改成了for
?为什么在
reducer
的调用过程中不允许dispatch(action)
?...
水平有限,有写的不好或不对的地方请指出,欢迎留issue交流😆
文件结构
Redux的文件结构并不复杂,每个文件就是一个对外导出的函数,依赖很少,分析起来也比较容易,只要会用Redux基本上都能看懂本文。
这是Redux的目录结构:
源码分析
源码分析的顺序推荐如下,就是跟着pipeline的顺序来
主题思路我会写出来,很细节的部分就直接写在代码注释里了。
index
只有两个功能:
createStore
createStore 由于有两种生成 store 的方法,所以起手先确定各个参数
然后声明中间变量,后面会讲到这些中间变量的作用
然后看怎么订阅一个事件,其实这就是一个发布-订阅模式,但是和普通的发布订阅模式不同的是, 多了一个
ensureCanMutateNextListeners
函数。 我去翻了一下redux的commit message,找到了对listener做深拷贝的原因:https://github.com/reactjs/redux/issues/461,简单来说就是在listener中可能有unsubscribe操作,比如有3个listener(下标0,1,2),在第2个listener执行时unsubscribe了自己,那么第3个listener的下标就变成了1,但是for循环下一轮的下标是2,第3个listener就被跳过了,所以执行一次深拷贝,即使在listener过程中unsubscribe了也是更改的nextListeners(nextListeners会去深拷贝currentListeners)。当前执行的currentListeners不会被修改,也就是所谓的快照。redux在执行subscribe和unsubscribe的时候都要执行ensureCanMutateNextListeners来确定是否要进行一次深拷贝,只要执行dispatch,那么就会被
const listeners = (currentListeners = nextListeners)
,所以currentListeners === nextListeners,之后的subscribe和unsubscribe就必须深拷贝一次, 否则可以一直对nextListeners操作而不需要为currentListeners拷贝赋值,即只在必要时拷贝。接下来看 dispatch 这个函数,可以看到每次dispatch时会
const listeners = (currentListeners = nextListeners)
,为可能到来的mutateNextListener做好准备。接下来看
getState
,就是一个returnobservable函数,这个是为了配合RxJS使用,如果不使用RxJS可以忽略,在这里略过。
replaceReducer函数是替换整个store的reducer,一般不经常用到,代码也含简单,换个reducer重新init一下
最后,暴露的接口的功能都已经具备了,还需要取一下默认值,你可能会说不是已经有preloadedState了吗但是默认值不是只有一个的,每个reducer都可以指定对应部分的state的默认值,那些默认值需要先经过一个action的洗礼才可以被赋值,还记得reducer要求每个不可识别的action.type返回原始state吗?就是为了取得默认值。
为了保证这个type是无法识别的,被定义成了一个随机值
至此,我们的已经能够createStore,getState,subscribe,unsubscribe,dispatch了
combineReducer
combineReducer的代码挺长的,但是主要都是用来检查错误了,核心代码就是将要合并的代码组织组织成一个树结构,然后将传入的reduce挨个跑action,跑出的新的state替换掉原来的state,因为无法识别的action会返回原来的state,所以大部分无关的reducer会返回相同引用的state,只有真正捕获action的reducer会返回新的state,这样做到了局部更新,否则每次state的一部分更新导致所有的state都原地深拷贝一次就麻烦了。
bindActionCreators
这个用的不多,一般是为了方便,直接
import *
来引入多个actionCreators,原理很简单:实际上就是返回一个高阶函数,通过闭包引用,将 dispatch 给隐藏了起来,正常操作是发起一个 dispatch(action),但bindActionCreators 将 dispatch 隐藏,当执行bindActionCreators返回的函数时,就会dispatch(actionCreators(...arguments))。所以参数叫做 actionCreators,作用是返回一个 action如果是一个对象里有多个 actionCreators 的话,就会类似 map 函数返回一个对应的对象,每个 key 对应的 value 就是上面所说的被绑定了的函数。
applyMiddleware
精髓来了,这个函数最短但是最精髓,这个 middleware 的洋葱模型的思想是从koa的中间件拿过来的,我没看过koa的中间件(因为我连koa都没用过...),但是重要的是思想。
放上redux的洋葱模型的示意图(via 中间件的洋葱模型)
单独理解太晦涩,放一个最简单的
redux-thunk
帮助理解。redux-thunk:
最难理解的就是那三个柯里化箭头,这三个箭头相当于欠了真正的middleware实体三个参数
{dispatch, getState}
,next
,action
,作为一个中间件,就是将这几个像管道一样在各个中间件中传递,与此同时加上一些副作用,比如后续管道的走向或者发起异步请求等等。那么开始看欠的这三个参数是怎么还给中间件的,代码不长,所以直接写在注释里了,一行一行看就可以。
applyMiddleware:
The text was updated successfully, but these errors were encountered: