Redux从设计到源码

Redux背后的设计思想

在讲设计思想前,先简单讲下Redux是什么?我们为什么要用Redux?

Redux是什么?

Redux是JavaScript状态容器,能提供可预测化的状态管理。

它认为:

  • Web应用是一个状态机,视图与状态是一一对应的。
  • 所有的状态,保存在一个对象里面。

我们先来看看“状态容器”、“视图与状态一一对应”以及“一个对象”这三个概念的具体体现。

image.png

如上图,Store是Redux中的状态容器,它里面存储着所有的状态数据,每个状态都跟一个视图一一对应。

Redux也规定,一个State对应一个View。只要State相同,View就相同,知道了State,就知道View是什么样,反之亦然。

比如,当前页面分三种状态:loading(加载中)、success(加载成功)或者error(加载失败),那么这三个就分别唯一对应着一种视图。

现在我们对“状态容器”以及“视图与状态一一对应”有所了解了,那么Redux是怎么实现可预测化的呢?我们再来看下Redux的工作流程。

image.png

首先,我们看下几个核心概念:

  • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。
  • State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
  • Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
  • Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
  • Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
  • dispatch:是View发出Action的唯一方法。

然后我们过下整个工作流程:

  1. 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
  2. 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
  3. State一旦有变化,Store就会调用监听函数,来更新View。

到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。

为什么要用Redux?

前端复杂性的根本原因是大量无规律的交互和异步操作。

变化和异步操作的相同作用都是改变了当前View的状态,但是它们的无规律性导致了前端的复杂,而且随着代码量越来越大,我们要维护的状态也越来越多。

我们很容易就对这些状态何时发生、为什么发生以及怎么发生的失去控制。那么怎样才能让这些状态变化能被我们预先掌握,可以复制追踪呢?

这就是Redux设计的动机所在。

Redux试图让每个State变化都是可预测的,将应用中所有的动作与状态都统一管理,让一切有据可循。

image.png

如上图所示,如果我们的页面比较复杂,又没有用任何数据层框架的话,就是图片上这个样子:交互上存在父子、子父、兄弟组件间通信,数据也存在跨层、反向的数据流。

这样的话,我们维护起来就会特别困难,那么我们理想的应用状态是什么样呢?看下图:

image.png

架构层面上讲,我们希望UI跟数据和逻辑分离,UI只负责渲染,业务和逻辑交由其它部分处理,从数据流向方面来说, 单向数据流确保了整个流程清晰。

我们之前的操作可以复制、追踪出来,这也是Redux的主要设计思想。

综上,Redux可以做到:

  • 每个State变化可预测。
  • 动作与状态统一管理。

Redux思想追溯

Redux作者在Redux.js官方文档Motivation一章的最后一段明确提到:

Following in the steps of Flux, CQRS, and Event Sourcing , Redux attempts to make state mutations predictable
by imposing certain restrictions on how and when updates can happen.

我们就先了解下Flux、CQRS、ES(Event Sourcing 事件溯源)这几个概念。

什么是ES?

  • 不是保存对象的最新状态,而是保存对象产生的事件。
  • 通过事件追溯得到对象最新状态。

举个例子:我们平常记账有两种方式,直接记录每次账单的结果或者记录每次的收入/支出,那么我们自己计算的话也可以得到结果,ES就是后者。

image.png

与传统增删改查关系式存储的区别:

  • 传统的增删是以结果为导向的数据存储,ES是以过程为导向存储。
  • CRUD是直接对库进行操作。
  • ES是在库里存了一系列事件的集合,不直接对库里记录进行更改。

优点:

  • 高性能:事件是不可更改的,存储的时候并且只做插入操作,也可以设计成独立、简单的对象。所以存储事件的成本较低且效率较高,扩展起来也非常方便。
  • 简化存储:事件用于描述系统内发生的事情,我们可以考虑用事件存储代替复杂的关系存储。
  • 溯源:正因为事件是不可更改的,并且记录了所有系统内发生的事情,我们能用它来跟踪问题、重现错误,甚至做备份和还原。

缺点:

  • 事件丢失:因为ES存储都是基于事件的,所以一旦事件丢失就很难保证数据的完整性。
  • 修改时必须兼容老结构:指的是因为老的事件不可变,所以当业务变动的时候新的事件必须兼容老结构。

CQRS(Command Query Responsibility Segregation)是什么?

顾名思义,“命令与查询职责分离”-->”读写分离”。

image.png

整体的思想是把Query操作和Command操作分成两块独立的库来维护,当事件库有更新时,再来同步读取数据库。

看下Query端,只是对数据库的简单读操作。然后Command端,是对事件进行简单的存储,同时通知Query端进行数据更新,这个地方就用到了ES。

优点:

  • CQ两端分离,各自独立。
  • 技术代码和业务代码完全分离。.

缺点:

  • 强依赖高性能可靠的分布式消息队列。

Flux是什么?

Flux是一种架构思想,下面过程中,数据总是“单向流动”,任何相邻的部分都不会发生数据的“双向流动”,这保证了流程的清晰。Flux的最大特点,就是数据的“单向流动”。

image.png
  1. 用户访问View。
  2. View发出用户的Action。
  3. Dispatcher收到Action,要求Store进行相应的更新。
  4. Store更新后,发出一个“change”事件。

介绍完以上之后,我们来整体做一下对比。

CQRS与Flux

相同:当数据在write side发生更改时,一个更新事件会被推送到read side,通过绑定事件的回调,read side得知数据已更新,可以选择是否重新读取数据。

差异:在CQRS中,write side和read side分属于两个不同的领域模式,各自的逻辑封装和隔离在各自的Model中,而在Flux里,业务逻辑都统一封装在Store中。

Redux与Flux

Redux是Flux思想的一种实现,同时又在其基础上做了改进。Redux还是秉承了Flux单向数据流、Store是唯一的数据源的思想。

image.png

最大的区别:

  • Redux只有一个Store。

Flux中允许有多个Store,但是Redux中只允许有一个,相较于Flux,一个Store更加清晰,容易管理。Flux里面会有多个Store存储应用数据,并在Store里面执行更新逻辑,当Store变化的时候再通知controller-view更新自己的数据;Redux将各个Store整合成一个完整的Store,并且可以根据这个Store推导出应用完整的State。

同时Redux中更新的逻辑也不在Store中执行而是放在Reducer中。单一Store带来的好处是,所有数据结果集中化,操作时的便利,只要把它传给最外层组件,那么内层组件就不需要维持State,全部经父级由props往下传即可。子组件变得异常简单。

  • Redux中没有Dispatcher的概念。

Redux去除了这个Dispatcher,使用Store的Store.dispatch()方法来把action传给Store,由于所有的action处理都会经过这个Store.dispatch()方法,Redux聪明地利用这一点,实现了与Koa、RubyRack类似的Middleware机制。Middleware可以让你在dispatch action后,到达Store前这一段拦截并插入代码,可以任意操作action和Store。很容易实现灵活的日志打印、错误收集、API请求、路由等操作。

除了以上,Redux相对Flux而言还有以下特性和优点:

  • 文档清晰,编码统一。
  • 逆天的DevTools,可以让应用像录像机一样反复录制和重放。

目前,美团外卖后端管理平台的上单各个模块已经逐步替换为React+Redux开发模式,流程的清晰为错误追溯和代码维护提供了便利,现实工作中也大大提高了人效。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容