Redux 背后的Flux 架构思想
看源码首先得明白,Redux背后的架构思想,为什么他和React那么的搭配,我们可以认为 Redux 是 Flux 的一种实现形式(虽然它并不严格遵循 Flux 的设定),理解 Flux 将帮助你更好地从抽象层面把握 Redux。
首先flux架构将一个应用分成4个部分:
- view: 视图层
- action(动作):视图层发出的动作如: mouseclick
- dispatcher (派发器) :主要是用来接受actions,执行回调函数
- store(数据层):用来存放应用的状态,一旦发生变动就要提醒 view 视图层更新
从图中可以看出Flux最大的特点就是 ———— 单向数据流
- 用户访问view
- view发出用户的action
- dispatch 收到action, 则要求store 就行数据更新
- store 更新后 发出一个改变事件
- 接收到change视图更新
读到这里肯定就有人问? 就因为他这样所以我们要采用这种方式,因为前端项目有大多数是mvc 或者是mvvm架构。这种架构有什么缺点,有一个很大的缺点就是 当业务复杂度变得越来越高的时候,因为允许view 层和model层 直接传递。因为model可能不止对应一个view 层,就会出现下图这样的情况:
从图上看数据流这是真的混乱,如果项目中出现bug就很难定位到到底是哪一步出现问题,所以Flux 的核心是单向数据流, 因为视图更新就是从store通知视图更新。
Redux的架构其实是和Flux是非常像的:理解了Flux,自然理解了Redux, 毕竟整体的架构思想是非常像的。废话不多说吧,直接看图:
我给大家手动模拟下整个流程,用户点击鼠标发出一个action,经过Actions的处理,Actions 其实就是返回一个对象, 对象里面包含type 和所需要的数据 ,数据传到了Reducer 本质是一个纯函数,输入什么,输出什么,不做任何逻辑的运算。返回一个新的State之后,传递到数据中心Store,由Store通知视图更新。整个流程就结束了。那么Redux到底是怎么做到的,带着好奇心,我和大家一起撸一遍源码,看他里面到底有什么奥妙?
Redux源码解析
Redux 源码的目录结构十分简单, types 主要存放的是ts 定义的一些类型, utils主要是一些通用方法,没什么涉及关键流程的,所以接下来我就主要分析 applyMiddleware 、 combinReducers、compose、 createStrore这三个ts 文件
createStore作为Redux的开始 我们先分析这个文件
CreateStore
// 引入 redux import { createStore } from 'redux' // 创建 store const store = createStore( reducer, initial_state, applyMiddleware(middleware1, middleware2, ...) ); 复制代码
从图中createStore 接受3个参数
- 第一个参数就是一个reducer 一个纯函数 ,由我们自己定义
- 第二个参数 初始化的数状态
- 第三个参数 其实就是制定中间件 在源码中就是enhancer 增强store
从拿到入参到返回出 store 的过程中,到底都发生了什么呢?这里我为你提取了
createStore 中主体逻辑的源码(解析在注释里):
这段代码主要是做一些类型判断, 和一些写法兼容没什么。继续往下看, 下面是一些初始状态的赋值:
肯定有部分同学对这里的快照和浅拷贝确保不同的引用有疑问? 我这里先卖个关子,等整个流程走完后面重点分析why?? 接下来就进入我们经常用的getState函数了。
getState:
我草就这么几行代码,十分的简单,源码也就那样嘛,easy easy! 继续往下看
subscribe:
这里订阅的时候浅拷贝了一下,卸载的时候也浅拷贝,用的都是nextListeners, 还记得我们有个currentListeners吧, 难道说这个一点用都没有嘛。 我们接着往下看。
dispatch:
dispatch 的时候: 又将next 重新复制给 current 然后执行每个listenr. 看到这里我想你应该明白reducer 中 我dispatch ? 或者做一些subscribe 做一些脏操作,redux 源码中为了防止这种 就是设置 isDispatching 这个 变量来控制。
所以dispacth一个action? Redux 帮我们做了啥事, 就很简单2件事
- oldState 经过reducer 产生了newState, 更新了store数据中心
- 触发订阅
整个Redux 的工作流,到这里其实已经结束了, 但是我们还有一个疑问就是 subscribe 为啥都是nextListeners 然后在dispatch 的 又把值重新赋给currentListeners? 这是为什么呢??
答案就是:为了保证触发订阅的稳定性
这句话怎么理解呢我举一个例子:
// 定义监听函数a function listenera() { } // 订阅 a,并获取 a 的解绑函数 const unSubscribea = store.subscribe(listenera) // 定义监听函数 b function listenerb() { // 在 b 中解绑 a unSubscribea() } // 定义监听函数 c function listenerc() { } // 订阅 b store.subscribe(listenerb) // 订阅 c store.subscribe(listenerc) 复制代码
从上文我可以得知当前的currentListeners:
[listenera, listenerb, listenerc]
但是比较特殊的是listenb 其实卸载 listena的, OK如果我们不浅拷贝一下, 那么触发订阅的时候数组遍历到 i = 2 的时候其实数组是undefined , 这样引发报错, 因为我们在 订阅前和卸载订阅都浅拷贝一下,nextListeners数据随便怎么变, 只要保证currentListener 稳定 就好了。
本次dispacth完之后,下一次dispacth 假设没有新增订阅, 数据关系又重新赋值。
listeners =( currentListeners = nextListeners)
你仔细回想一下这个变化,是不是所有就理解的通了, 这也是为什么Redux 订阅稳定的原因了啦。设计真的是十分的巧妙哇,读到现在发现源码其实并没有想象的辣么难? 细节处理满分哇。 接下来就是分析Redux的中间件模型。