redux 的思想前面已经介绍过了。本文主要简单的看下,redux 的具体实现。redux 主要提供了如下几个功能:
- 创建 store,即:createStore()。
- 创建出来的 store 提供subscribe,dispatch,getState这些方法。
- 将多个reducer合并为一个reducer,即:combineReducers()。
- 应用中间件,即applyMiddleware()。
createStore 实现
简化版代码如下:
|
|
getState
可以看出,createStore 将 state 通过闭包保存在了 currentState 中,通过调用 getState 返回。为了能够初始化数据,在 createStore 的最后,dispath 了一个 ActionTypes.INIT
请求。在这里 ActionTypes.INIT
实际上为一个随机字符串 @@redux/INIT${randomString()}
,这就保证了其不会命中任何 action
,而是走默认 return state
完成数据初始化过程。
subscribe
一个简单的发布订阅模式,所有注册函数保存在 newListeners (执行的时候,会用 currentListeners) 队列中。返回一个 unsubscribe 来清除注册函数。
dispatch
直接调用 reducer 函数来进行处理,并且执行所有的 listeners。可以看见整个过程都是同步的,这也是 redux 不能在 reducer 中书写异步的原因之一(最主要的还是因为纯函数,不能涉及IO)。
这里 dispatch 对参数 action 做了检查:action 必须是一个纯对象,且必须有 type 属性。
combinReducers
combinReducers 可以将多个 reducers 合并在成为一个 reducers,从而给 createStore 使用。简单的用法如下。简单来说就是讲,各个 reducer 上的 state 统一管理在一个 key 值上,action 判断都统一放在一起。
|
|
|
|
这里我们可以看见,融合后的 reducer 当一个 action 触发时,不仅仅是该 action 对应的 reducer 执行了,其他所有的 reducer 也同时被执行了,只是 action type 不匹配,state 的值不变而已。这种空操作,可能会存在性能的浪费。(理论上大部分都不会有问题,只有节点数量多了才会有,最重要的优化是:没问题之前不做优化)
我们可以借助 redux-ignore 来指定黑白名单的方式,返回优化后的 reducer。
|
|
原理也很简单,就是利用 actions.indexOf(action.type) >= 0 来决定 reducer 是否触发而已。
applyMiddleware 中间件
我们知道 reducer 本身是一个纯函数,纯函数要求自身不能和 IO 产生关联。这也就注定 reducer 本身是一个同步函数。同样,我们在 store.dispath
源码中看见,当一个 dispath 发出后,所有的监听回调也就同步发生了。
在实际业务中,我们的请求一般都是异步的,那么做异步请求处理就只能放在 dispatch 前来做。所以我们希望有一种通用的方案,来扩展 dispath 的功能。这就是中间件的功能。下面我们思考因该如何来做一个中间件:
封装 dispath
加入我们想要加入一个 dispath 前后记录日志的功能。我们可能会这么写
|
|
为了方便复用,我们扩展其为一个函数:
|
|
重写 dispatch
现在我们可以通过调用 dispatchAndLog
来完成带有日志的 dispath。日志记录功能可以直接反映到 dispath 上面,不改变原有的用法,更直接些。通过复写 dispath 来实现扩展:
|
|
现在我们随便使用 dispath 就可以实现日志的打印,而不用费事的去使用 dispatchAndLog
函数。
新的扩展
实际开发中,我们可能不仅需要日志功能,还有可能需要其他的扩展功能。比如异常上报。为此我们需要将上述操作写成两个函数:
|
|
使用两个扩展:
|
|
middleware 方法
从上面,我们可以看出,其实扩展多个中间件,就是复写多次 store.dispatch
方法。每次 next 缓存的都是上一个中间件替换好的 dispatch 方法。即如下链式调用:
|
|
即洋葱模型。我们提供这样一个封装函数,来实现这一模型。
|
|
对 extendsLogFunc
稍作改动(extendsErrorPublish, 同理)
|
|
就可以使用了:
|
|
函数柯里化
我们观察改动后的 extendsLogFunc。无非就是层层准备参数,最后一起使用,这样我们很容易想到函数柯里化:
|
|
为了能顺利运行,我们也需要晒微修改下 applyMiddleware,如下:
|
|
我们再看,createStore 的源码,如果我们传入了第三个参数 enhancer 作为中间件,那么调用的是:
|
|
典型的柯里化调用,为此,我们最后修改下 applyMiddleware
函数:
|
|
看下源码
|
|
和我们的书写思路差不多吧,在源码里,通过 compose 方法,来实现的洋葱模型;compose 函数式编程中用来复合代码的:
|
|
在搭配 react 的时候,我们通常不会直接使用 redux
因为这样需要关注的优化点就有点多。通常情况下,我们会选择 react-redux
来完成和 react 的结合。下篇文章着重介绍下,react-redux
源码,并针对 redux 可能带来的性能问题进行说明。