范式
与其说Redux是一个工具类库,我更想说它是一套处理状态与数据变更的范式。官方有明确的一些规则,社区也累积了很多最佳实践。
从谷歌搜索来看,这是个很有趣的现象。
至于个人看法则是根据项目和团队实际情况来选用Redux方案,而非强制上马,避免未来可能会背的技术债。
Redux Flow
Redux是单向数据流,其工作流如下图所示(看不懂或者没用过的小伙伴可以先看看Redux文档,用一用):
在Redux中,Store中state的改变只能由action触发,经过reducer,从而改变state,因为在Redux中,是不允许直接改变state的。至于这一点,下一篇博客会谈到为什么。
这样的特性也就决定了,我们需要编写大量的Action和Reducer代码,从而给人一种很繁琐的感觉。从而带来一些不必要的开销与麻烦。因此是不怎么适合小项目的。
取舍
至于这一点,社区则有一篇文章讲述了这个:You Might Not Need Redux
而Redux与Flux的作者也表达过:
我想修正一个观点:当你在使用 React 遇到问题时,才使用 Redux。
你应当清楚何时需要 Flux。如果你不确定是否需要它,那么其实你并不需要它。
在Redux的文档中,则有:
在打算使用 Redux 的时候进行权衡是非常重要的。它从设计之初就不是为了编写最短、最快的代码,他是为了解决 “当有确定的状态发生改变时,数据从哪里来” 这种可预测行为的问题的。它要求你在应用程序中遵循特定的约定:应用的状态需要存储为纯数据的格式、用普通的对象描述状态的改变、用不可更新的纯函数式方式来处理状态变化。这也成了抱怨是“样板代码”的来源。这些约束需要开发人员一起来努力维护,但也打开了一扇扇可能的大门(比如:数据持久性、同步)。
原理
至于Redux实现的原理,就得看它的源代码了,就像上一篇博客所说的,Redux的源代码简洁且有力。
今天则从Redux最重要的方法createStore()
说起,来看看Redux的内部实现。
下面是简化后的代码,提取了Redux的createStore()
的主干部分。
1 | function createStore (reducer, preloadedState, enhancer) { |
下面则针对各个问题,来解读这份源代码。
1. 为什么不能直接修改state,怎么做到的
在createStore
函数中,初始化state为preloadedState,如果preloadedState不存在则为undefined。
而我们要拿到store的state,只能通过store的getState
函数做到。函数内容如下:
1 | /** |
至于不可直接更改的原因则是currentState作为私有变量,只能被内部访问,没有暴露在公共接口中。
这也算是闭包的一种应用吧。
这儿也解决了一个我的小疑惑,就是频繁调用getState()
开销如何?
现在看来,也只是和直接读取 state 的开销差不多,微乎其微。
2. 怎么通过subscribe来Redux state的更新
这点的话,当时看了看源代码,便了然了。
Redux内部实现了个订阅者模式,subscribe则是添加订阅。
而在dispatch函数中,底部有这么一段:
1 | const listeners = currentListeners = nextListeners |
3. state是怎么更新的
这个问题则涉及到了 dispatch
函数,在 dispatch
函数内部有这么一段:
1 | function dispatch(action) { |
也就是说当使用dispatch
函数触发action
时,currentReducer
计算并返回新的state,完成这次更新。
令我感到注目的是,这儿用了 try..catch..finally这种方式,但是并没有catch部分,于是自己实践了一下:
1 | try { |
对此控制台的结果如下:
也就是说在reducer计算新的state时,错误会抛出,但是函数却能继续运行,不至于直接崩溃。
感觉这个方法对于某些特定场景还是很有用的。
4. ensureCanMutateNextListeners是什么
在createStore
的源代码中,有一个函数是 ensureCanMutateNextListeners
,内容是:
1 | function ensureCanMutateNextListeners() { |
很多函数在执行时都调用了它,开始还不是很理解为什么,然后找了找别人的解释:
同时有一个辅助方法ensureCanMutateNextListeners()。这是考虑到,在执行某个监听函数的时候,可能会添加新的监听函数,或者取消某个监听函数。为了让这些改变不影响当前的监听函数列表的执行,因此在改变之前,先拷贝一份副本(即nextListeners),然后对该副本进行操作,从而所有的改变会在下一次dispatch(action)的时候生效。 – Redux入门
Redux的适配
Redux从来就不想只做React的工具,他的目标是和各大框架配合使用。而事实上各大框架也有Redux的适配库。
那么自己的问题就在于Redux怎么和别的工具去做一个适配。
一开始不理解,后面看了源代码,想了想这种观察者模式,与 createStore
返回的store对象所具有的内容,心里便了然了。
框架通过Redux的subscribe
方法,订阅state
更新,并通过dispatch
方法,触发state更新。
那么具体框架如何应用数据,就是框架内部业务层面的事情了。每个框架根据自身特点和需求处理的方式都不一样。有兴趣的可以看看 react-redux 和 revue