夫 子 说
元月二号欠下袋鼠云技术公号一篇关于 Redux 源码解读的文章,转眼月底,期间常被“债主”上门催债。由于年底项目工期比较紧,于是债务就这样被利滚利。但是好在这段时间有点闲暇,于是赶紧把这篇文章给完成了。据说文章点赞多了可以抵扣利息,小伙们要是觉得我这篇文章还不错的话,记得帮我点赞哦!好让我早日摆脱债务,感激不尽!
好了,回到正题。今天打算和大家讲一讲 redux 的源码,通过分析源码,我个人觉得受益匪浅,借此通过这篇文章把我的一些心得体会向大家分享一下,另外需要注意一下这次分享的源码用的 redux 的 V4.0.0 版本,小伙伴在对照的时候可别搞错咯。接下来老司机可是要发车了,大家抓紧时间上车哦!
在讲源码之前我们首先回顾一下 redux 是如何使用的,下面我们看一下使用 demo:
上面这段代码完整地展示了 redux 的使用(如果有小伙伴对这段代码不是很理解的话,建议先去学习 redux 的使用再来看这篇源码,这样更加事半功倍)。通过上段代码,我们拆分几个比较核心的点,我一一列举一下:
action 的结构是如何的?
如何去定义一个 reducer?
combineReducers 是如何整合多个 reducer 的?
createStore 是如何创建一个 store?
5.dispatch 拿到 action 到底干了什么?
subscribe 是如何监听状态发生改变的?
getState 是如何拿到所有的状态值的?
本期先解决前三个疑问,让我们一起去源码里寻找答案!
1、action 的结构是如何的?
首先得解释一下 action 是干嘛的,它是负责把数据从应用带到 store 里面,也是 store 的唯一数据来源,并由以下两个部分组成:
type(操作类型)
payload(携带的数据)
为什么得有这两个?其实也很好理解,我们拿银行来类比。某天,你拿着一万块来到银行,走到柜台,人业务员第一件事肯定是问你要办啥业务,存钱?转账?还是还贷?你得把这些告诉业务员,不然业务没法给你办理业务,因此我们 action 就得有一个 type,好让 reducer 知道你要干啥。当然,你办理存款或者是还款啥的,必不能少的就是毛爷爷了,payload 对应的值就好比这些毛爷爷。用一个话来总结 action 的作用就是:告诉 reducer 拿着 payload 去做 type 这件事。
2、如何去定义一个 reducer?
上面讲 action 的时候,提到了 reducer 了,这里还是拿我上面的银行做个类比,当我们拿着钱去银行存钱,我们不可能自己去银行把银行保险柜打开,完了把钱放进去,这样是不允许的,我们得需要业务员这个中间人去帮我们做存钱这件事,而业务员所扮演的角色正好就是 reducer 所要担任的角色。接下来讲一下如何去定义一个 reducer,其实 reducer 的写法并没有绝对的写法,只要符合下面几个条件都能称之为 reducer:
必须得是一个函数。
函数接收两个参数。第一个:该 reducer 所负责的 state。第二个:action。
函数体内部可以针对不同的 action 的 type 做出响应,这里你可以 if-else 或者 switch-case 都是可以的。
函数必须有返回值。当修改 state 了之后,必须将修改后的 state 返回。如果遇到未知的 type 则需要返回一个默认值。
3、combineReducers 是如何整合多个 reducer 的?
我们先看一下 combineReducers 传入的参数:
combineReducers 接受的是一个参数首先得是对象,其次该对象每一个属性对应一个 reducer。搞清楚 combineReducers 的结构之后,我们再看一下 combineReducers 对其做了哪些处理。
第一步:浅拷贝 reducers
这里定义了一个 finalReducers 和 finalReducerKeys,分别用来拷贝 reducers 和其属性。先用 Object.keys 方法拿到 reducers 所有的属性,然后进行 for 循环,每一项可根据其属性拿到对应的 reducer,并浅拷贝到 finalReducers 中,但是前提条件是每个 reducer 的类型必须是 Function,不然会直接跳过不拷贝。
第二步:检测 finalReducers 里的每个 reducer 是否都有默认返回值
assertReducerShape 方法主要检测两点:
不能占用 redux 内部特有的命名空间
如果遇到未知的 action 的类型,reducer 不能返回 undefined,得返回默认的值
如果传入 type 为 @@redux/INIT< 随机值 > 的 action,返回 undefined,说明没有对未 知的 action 的类型做响应,需要加默认值。如果对应 type 为 @@redux/INIT< 随机值 > 的 action 返回不为 undefined, 但是却对应 type 为 @@redux/PROBE_UNKNOWN_ACTION_< 随机值 > 返回为 undefined,说明占用了 命名空间。整个逻辑相对简单,好好自己梳理一下。
第三步:返回一个函数,用于代理所有的 reducer
先对传入的 state 用 getUnexpectedStateShapeWarningMessage 做了一个异常检测,找出 state 里面没有对应 reducer 的 key,并提示开发者做调整。接着我们跳到 getUnexpectedStateShapeWarningMessage 里,看其实现。
getUnexpectedStateShapeWarningMessage 接收四个参数 inputState(state)、reducers(finalReducers)、action(action)、unexpectedKeyCache(unexpectedKeyCache),这里要说一下 unexpectedKeyCache 是上一次检测 inputState 得到的其里面没有对应的 reducer 集合里的异常 key 的集合。整个逻辑如下:
前置条件判断,保证 reducers 集合不为 {} 以及 inputState 为简单对象
找出 inputState 里有的 key 但是 reducers 集合里没有 key
如果是替换 reducer 的 action, 跳过第四步,不打印异常信息
将所有异常的 key 打印出来
getUnexpectedStateShapeWarningMessage 分析完之后,我们接着看后面的代码。
首先定义了一个 hasChanged 变量用来表示 state 是否发生变化,遍历 reducers 集合, 将每个 reducer 对应的原 state 传入其中,得出其对应的新的 state。紧接着后面对新的 state 做了一层未定义的校验,函数 getUndefinedStateErrorMessage 的代码如下:
逻辑很简单,仅仅做了一下错误信息的拼接。未定义校验完了之后,会跟原 state 作对比,得出其是否发生变化。最后发生变化返回 nextState,否则返回 state。
未完待续
下期预告
《技本功丨知否知否,Redux 源码竟如此意味深长(下集)》
THE END
最后,袋萌萌感谢每一位老铁 2018 年的陪伴,生死看淡,不服就干!2019,咱们再战,不断进步!