带你从头到尾系统地撸一遍Redux源码(一)

简介: Redux 背后的Flux 架构思想看源码首先得明白,Redux背后的架构思想,为什么他和React那么的搭配,我们可以认为 Redux 是 Flux 的一种实现形式(虽然它并不严格遵循 Flux 的设定),理解 Flux 将帮助你更好地从抽象层面把握 Redux。首先flux架构将一个应用分成4个部分:view:  视图层action(动作):视图层发出的动作如: mouseclickdispatcher (派发器) :主要是用来接受actions,执行回调函数store(数据层):用来存放应用的状态,一旦发生变动就要提醒 view 视图层更新从图中可以看出Flux最大的

Redux 背后的Flux 架构思想



看源码首先得明白,Redux背后的架构思想,为什么他和React那么的搭配,我们可以认为 Redux 是 Flux 的一种实现形式(虽然它并不严格遵循 Flux 的设定),理解 Flux 将帮助你更好地从抽象层面把握 Redux。


首先flux架构将一个应用分成4个部分:


  1. view:  视图层


  1. action(动作):视图层发出的动作如: mouseclick


  1. dispatcher (派发器) :主要是用来接受actions,执行回调函数


  1. store(数据层):用来存放应用的状态,一旦发生变动就要提醒 view 视图层更新


image.png


从图中可以看出Flux最大的特点就是 ———— 单向数据流


  1. 用户访问view


  1. view发出用户的action


  1. dispatch 收到action, 则要求store 就行数据更新


  1. store 更新后 发出一个改变事件


  1. 接收到change视图更新


读到这里肯定就有人问? 就因为他这样所以我们要采用这种方式,因为前端项目有大多数是mvc 或者是mvvm架构。这种架构有什么缺点,有一个很大的缺点就是 当业务复杂度变得越来越高的时候,因为允许view 层和model层 直接传递。因为model可能不止对应一个view 层,就会出现下图这样的情况:



image.png


从图上看数据流这是真的混乱,如果项目中出现bug就很难定位到到底是哪一步出现问题,所以Flux 的核心是单向数据流, 因为视图更新就是从store通知视图更新。


Redux的架构其实是和Flux是非常像的:理解了Flux,自然理解了Redux, 毕竟整体的架构思想是非常像的。废话不多说吧,直接看图:


image.png


我给大家手动模拟下整个流程,用户点击鼠标发出一个action,经过Actions的处理,Actions 其实就是返回一个对象, 对象里面包含type 和所需要的数据 ,数据传到了Reducer 本质是一个纯函数,输入什么,输出什么,不做任何逻辑的运算。返回一个新的State之后,传递到数据中心Store,由Store通知视图更新。整个流程就结束了。那么Redux到底是怎么做到的,带着好奇心,我和大家一起撸一遍源码,看他里面到底有什么奥妙?


Redux源码解析



image.png


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 中主体逻辑的源码(解析在注释里):


image.png



这段代码主要是做一些类型判断, 和一些写法兼容没什么。继续往下看, 下面是一些初始状态的赋值:


image.png



肯定有部分同学对这里的快照和浅拷贝确保不同的引用有疑问? 我这里先卖个关子,等整个流程走完后面重点分析why?? 接下来就进入我们经常用的getState函数了。


getState:


image.png


我草就这么几行代码,十分的简单,源码也就那样嘛,easy easy! 继续往下看


subscribe:


image.png


这里订阅的时候浅拷贝了一下,卸载的时候也浅拷贝,用的都是nextListeners, 还记得我们有个currentListeners吧, 难道说这个一点用都没有嘛。 我们接着往下看。


dispatch:


image.png


dispatch 的时候: 又将next 重新复制给 current 然后执行每个listenr.  看到这里我想你应该明白reducer 中 我dispatch ? 或者做一些subscribe  做一些脏操作,redux 源码中为了防止这种 就是设置 isDispatching 这个 变量来控制。


所以dispacth一个action? Redux 帮我们做了啥事, 就很简单2件事


  1. oldState 经过reducer 产生了newState, 更新了store数据中心


  1. 触发订阅


整个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的中间件模型。


相关文章
|
Python
Windows7 设置pip 镜像 Pip Warning:–trusted-host 问题解决方案
最近写了一篇关于“微软开源分布式高性能GB框架LightGBM安装使用”的文章,有小伙伴安装Python环境遇到了问题。我个人也尝试安装了一下,确实遇到了很多问题。这不又遇到;设置pip 镜像 Pip Warning:–trusted-host 问题。
3220 0
|
前端开发 Java Linux
cp: can‘t stat ‘/usr/share/zoneinfo/Asia/Shanghai‘: No such file or directory
cp: can‘t stat ‘/usr/share/zoneinfo/Asia/Shanghai‘: No such file or directory
|
6月前
|
机器学习/深度学习 人工智能 自然语言处理
AI训练师入行指南(三):成熟AI模型与自研如何选择?
本文为AI训练师提供选型指南,探讨使用成熟模型还是自研算法。内容涵盖NLP、CV和多模态场景下主流模型推荐,如DeepSeek-Chat、GPT-4o、ResNet-50等,以及自研模型的应用场景与技术实现。同时提供懒人四步决策法和避雷口诀,帮助快速选择适合的工具。新手建议从预训练模型入手,逐步深入魔改或自研,避免常见坑点。附带场景化对比表,助力高效决策。
280 5
|
10月前
|
数据可视化 JavaScript
使用.sync 修饰符的最佳实践
在组件间通信时,使用 `.sync` 修饰符可以简化父子组件之间的双向数据绑定。通过在子组件中使用 `v-bind.sync`,父组件可以监听并同步子组件的属性变化,实现高效的数据传递和更新。
|
自动驾驶 5G 测试技术
5G NR中的帧结构是如何设计的?
【8月更文挑战第31天】
898 1
|
运维 Linux 数据安全/隐私保护
jumpserver详解(九)——jumpserver资产设置
jumpserver详解(九)——jumpserver资产设置
336 1
|
网络协议 安全 Linux
解密TCP连接断开:四次挥手的奥秘和数据传输的安全
本文将介绍TCP连接的断开过程,重点关注四次挥手的过程和状态变迁,以及为什么挥手需要四次和为什么需要TIME_WAIT状态。在TCP连接断开的过程中,双方需要发送FIN和ACK报文来确保数据的可靠传输和连接的正确关闭。挥手需要四次的原因是为了确保数据的完整传输和连接的可靠关闭。
1003 1
解密TCP连接断开:四次挥手的奥秘和数据传输的安全
|
存储 NoSQL Go
SSDB —— 开源NoSQL数据库 Redis之外的选择
SSDB是一个快速的用来存储十亿级别列表数据的开源 NoSQL 数据库。
900 0
SSDB —— 开源NoSQL数据库 Redis之外的选择
|
算法
【MATLAB】WOA鲸鱼算法优化的VMD信号分解算法
【MATLAB】WOA鲸鱼算法优化的VMD信号分解算法
1273 0
【MATLAB】WOA鲸鱼算法优化的VMD信号分解算法
|
算法 Java
JAVA中的分治法及其应用
JAVA中的分治法及其应用
98 1