Redux背后的設(shè)計(jì)思想
在講設(shè)計(jì)思想前恋捆,先簡(jiǎn)單講下Redux是什么?我們?yōu)槭裁匆肦edux急鳄?
Redux是什么谤民?
Redux是JavaScript狀態(tài)容器,能提供可預(yù)測(cè)化的狀態(tài)管理疾宏。
它認(rèn)為:
- Web應(yīng)用是一個(gè)狀態(tài)機(jī)张足,視圖與狀態(tài)是一一對(duì)應(yīng)的。
- 所有的狀態(tài)坎藐,保存在一個(gè)對(duì)象里面为牍。
我們先來(lái)看看“狀態(tài)容器”、“視圖與狀態(tài)一一對(duì)應(yīng)”以及“一個(gè)對(duì)象”這三個(gè)概念的具體體現(xiàn)岩馍。
如上圖碉咆,Store是Redux中的狀態(tài)容器,它里面存儲(chǔ)著所有的狀態(tài)數(shù)據(jù)蛀恩,每個(gè)狀態(tài)都跟一個(gè)視圖一一對(duì)應(yīng)疫铜。
Redux也規(guī)定,一個(gè)State對(duì)應(yīng)一個(gè)View双谆。只要State相同壳咕,View就相同,知道了State顽馋,就知道View是什么樣谓厘,反之亦然。
比如寸谜,當(dāng)前頁(yè)面分三種狀態(tài):loading(加載中)竟稳、success(加載成功)或者error(加載失敗)程帕,那么這三個(gè)就分別唯一對(duì)應(yīng)著一種視圖住练。
現(xiàn)在我們對(duì)“狀態(tài)容器”以及“視圖與狀態(tài)一一對(duì)應(yīng)”有所了解了,那么Redux是怎么實(shí)現(xiàn)可預(yù)測(cè)化的呢愁拭?我們?cè)賮?lái)看下Redux的工作流程讲逛。
首先,我們看下幾個(gè)核心概念:
- Store:保存數(shù)據(jù)的地方岭埠,你可以把它看成一個(gè)容器盏混,整個(gè)應(yīng)用只能有一個(gè)Store蔚鸥。
- State:Store對(duì)象包含所有數(shù)據(jù),如果想得到某個(gè)時(shí)點(diǎn)的數(shù)據(jù)许赃,就要對(duì)Store生成快照止喷,這種時(shí)點(diǎn)的數(shù)據(jù)集合,就叫做State混聊。
- Action:State的變化弹谁,會(huì)導(dǎo)致View的變化。但是句喜,用戶接觸不到State预愤,只能接觸到View。所以咳胃,State的變化必須是View導(dǎo)致的植康。Action就是View發(fā)出的通知,表示State應(yīng)該要發(fā)生變化了展懈。
- Action Creator:View要發(fā)送多少種消息销睁,就會(huì)有多少種Action。如果都手寫存崖,會(huì)很麻煩冻记,所以我們定義一個(gè)函數(shù)來(lái)生成Action,這個(gè)函數(shù)就叫Action Creator来惧。
- Reducer:Store收到Action以后檩赢,必須給出一個(gè)新的State,這樣View才會(huì)發(fā)生變化违寞。這種State的計(jì)算過(guò)程就叫做Reducer贞瞒。Reducer是一個(gè)函數(shù),它接受Action和當(dāng)前State作為參數(shù),返回一個(gè)新的State。
- dispatch:是View發(fā)出Action的唯一方法返敬。
然后我們過(guò)下整個(gè)工作流程:
- 首先,用戶(通過(guò)View)發(fā)出Action乒融,發(fā)出方式就用到了dispatch方法。
- 然后摄悯,Store自動(dòng)調(diào)用Reducer赞季,并且傳入兩個(gè)參數(shù):當(dāng)前State和收到的Action,Reducer會(huì)返回新的State
- State一旦有變化奢驯,Store就會(huì)調(diào)用監(jiān)聽(tīng)函數(shù)申钩,來(lái)更新View。
到這兒為止瘪阁,一次用戶交互流程結(jié)束撒遣∮寿耍可以看到,在整個(gè)流程中數(shù)據(jù)都是單向流動(dòng)的义黎,這種方式保證了流程的清晰禾进。
為什么要用Redux?
前端復(fù)雜性的根本原因是大量無(wú)規(guī)律的交互和異步操作廉涕。
變化和異步操作的相同作用都是改變了當(dāng)前View的狀態(tài)泻云,但是它們的無(wú)規(guī)律性導(dǎo)致了前端的復(fù)雜,而且隨著代碼量越來(lái)越大狐蜕,我們要維護(hù)的狀態(tài)也越來(lái)越多壶愤。
我們很容易就對(duì)這些狀態(tài)何時(shí)發(fā)生、為什么發(fā)生以及怎么發(fā)生的失去控制馏鹤。那么怎樣才能讓這些狀態(tài)變化能被我們預(yù)先掌握,可以復(fù)制追蹤呢娇哆?
這就是Redux設(shè)計(jì)的動(dòng)機(jī)所在湃累。
Redux試圖讓每個(gè)State變化都是可預(yù)測(cè)的,將應(yīng)用中所有的動(dòng)作與狀態(tài)都統(tǒng)一管理碍讨,讓一切有據(jù)可循治力。
如上圖所示,如果我們的頁(yè)面比較復(fù)雜勃黍,又沒(méi)有用任何數(shù)據(jù)層框架的話宵统,就是圖片上這個(gè)樣子:交互上存在父子、子父覆获、兄弟組件間通信马澈,數(shù)據(jù)也存在跨層、反向的數(shù)據(jù)流弄息。
這樣的話痊班,我們維護(hù)起來(lái)就會(huì)特別困難,那么我們理想的應(yīng)用狀態(tài)是什么樣呢摹量?看下圖:
架構(gòu)層面上講涤伐,我們希望UI跟數(shù)據(jù)和邏輯分離,UI只負(fù)責(zé)渲染缨称,業(yè)務(wù)和邏輯交由其它部分處理凝果,從數(shù)據(jù)流向方面來(lái)說(shuō), 單向數(shù)據(jù)流確保了整個(gè)流程清晰。
我們之前的操作可以復(fù)制睦尽、追蹤出來(lái)器净,這也是Redux的主要設(shè)計(jì)思想。
綜上当凡,Redux可以做到:
- 每個(gè)State變化可預(yù)測(cè)掌动。
- 動(dòng)作與狀態(tài)統(tǒng)一管理四啰。
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 事件溯源)這幾個(gè)概念柑晒。
什么是ES?
- 不是保存對(duì)象的最新?tīng)顟B(tài)眷射,而是保存對(duì)象產(chǎn)生的事件匙赞。
- 通過(guò)事件追溯得到對(duì)象最新?tīng)顟B(tài)。
舉個(gè)例子:我們平常記賬有兩種方式妖碉,直接記錄每次賬單的結(jié)果或者記錄每次的收入/支出涌庭,那么我們自己計(jì)算的話也可以得到結(jié)果,ES就是后者欧宜。
與傳統(tǒng)增刪改查關(guān)系式存儲(chǔ)的區(qū)別:
- 傳統(tǒng)的增刪是以結(jié)果為導(dǎo)向的數(shù)據(jù)存儲(chǔ)坐榆,ES是以過(guò)程為導(dǎo)向存儲(chǔ)。
- CRUD是直接對(duì)庫(kù)進(jìn)行操作冗茸。
- ES是在庫(kù)里存了一系列事件的集合席镀,不直接對(duì)庫(kù)里記錄進(jìn)行更改。
優(yōu)點(diǎn):
- 高性能:事件是不可更改的夏漱,存儲(chǔ)的時(shí)候并且只做插入操作豪诲,也可以設(shè)計(jì)成獨(dú)立、簡(jiǎn)單的對(duì)象挂绰。所以存儲(chǔ)事件的成本較低且效率較高屎篱,擴(kuò)展起來(lái)也非常方便。
- 簡(jiǎn)化存儲(chǔ):事件用于描述系統(tǒng)內(nèi)發(fā)生的事情葵蒂,我們可以考慮用事件存儲(chǔ)代替復(fù)雜的關(guān)系存儲(chǔ)交播。
- 溯源:正因?yàn)槭录遣豢筛牡模⑶矣涗浟怂邢到y(tǒng)內(nèi)發(fā)生的事情践付,我們能用它來(lái)跟蹤問(wèn)題堪侯、重現(xiàn)錯(cuò)誤,甚至做備份和還原荔仁。
缺點(diǎn):
- 事件丟失:因?yàn)镋S存儲(chǔ)都是基于事件的伍宦,所以一旦事件丟失就很難保證數(shù)據(jù)的完整性。
- 修改時(shí)必須兼容老結(jié)構(gòu):指的是因?yàn)槔系氖录豢勺兎α海援?dāng)業(yè)務(wù)變動(dòng)的時(shí)候新的事件必須兼容老結(jié)構(gòu)次洼。
CQRS(Command Query Responsibility Segregation)是什么?
顧名思義遇骑,“命令與查詢職責(zé)分離”-->”讀寫分離”卖毁。
整體的思想是把Query操作和Command操作分成兩塊獨(dú)立的庫(kù)來(lái)維護(hù),當(dāng)事件庫(kù)有更新時(shí),再來(lái)同步讀取數(shù)據(jù)庫(kù)亥啦。
看下Query端炭剪,只是對(duì)數(shù)據(jù)庫(kù)的簡(jiǎn)單讀操作。然后Command端翔脱,是對(duì)事件進(jìn)行簡(jiǎn)單的存儲(chǔ)奴拦,同時(shí)通知Query端進(jìn)行數(shù)據(jù)更新,這個(gè)地方就用到了ES届吁。
優(yōu)點(diǎn):
- CQ兩端分離错妖,各自獨(dú)立。
- 技術(shù)代碼和業(yè)務(wù)代碼完全分離疚沐。.
缺點(diǎn):
- 強(qiáng)依賴高性能可靠的分布式消息隊(duì)列暂氯。
Flux是什么?
Flux是一種架構(gòu)思想亮蛔,下面過(guò)程中痴施,數(shù)據(jù)總是“單向流動(dòng)”,任何相鄰的部分都不會(huì)發(fā)生數(shù)據(jù)的“雙向流動(dòng)”究流,這保證了流程的清晰辣吃。Flux的最大特點(diǎn),就是數(shù)據(jù)的“單向流動(dòng)”梯嗽。
- 用戶訪問(wèn)View。
- View發(fā)出用戶的Action沽损。
- Dispatcher收到Action灯节,要求Store進(jìn)行相應(yīng)的更新。
- Store更新后绵估,發(fā)出一個(gè)“change”事件炎疆。
介紹完以上之后,我們來(lái)整體做一下對(duì)比国裳。
CQRS與Flux
相同:當(dāng)數(shù)據(jù)在write side發(fā)生更改時(shí)形入,一個(gè)更新事件會(huì)被推送到read side,通過(guò)綁定事件的回調(diào)缝左,read side得知數(shù)據(jù)已更新亿遂,可以選擇是否重新讀取數(shù)據(jù)。
差異:在CQRS中渺杉,write side和read side分屬于兩個(gè)不同的領(lǐng)域模式蛇数,各自的邏輯封裝和隔離在各自的Model中,而在Flux里是越,業(yè)務(wù)邏輯都統(tǒng)一封裝在Store中耳舅。
Redux與Flux
Redux是Flux思想的一種實(shí)現(xiàn),同時(shí)又在其基礎(chǔ)上做了改進(jìn)倚评。Redux還是秉承了Flux單向數(shù)據(jù)流浦徊、Store是唯一的數(shù)據(jù)源的思想馏予。
最大的區(qū)別:
- Redux只有一個(gè)Store。
Flux中允許有多個(gè)Store盔性,但是Redux中只允許有一個(gè)霞丧,相較于Flux,一個(gè)Store更加清晰纯出,容易管理蚯妇。Flux里面會(huì)有多個(gè)Store存儲(chǔ)應(yīng)用數(shù)據(jù),并在Store里面執(zhí)行更新邏輯暂筝,當(dāng)Store變化的時(shí)候再通知controller-view更新自己的數(shù)據(jù)箩言;Redux將各個(gè)Store整合成一個(gè)完整的Store,并且可以根據(jù)這個(gè)Store推導(dǎo)出應(yīng)用完整的State焕襟。
同時(shí)Redux中更新的邏輯也不在Store中執(zhí)行而是放在Reducer中陨收。單一Store帶來(lái)的好處是,所有數(shù)據(jù)結(jié)果集中化鸵赖,操作時(shí)的便利务漩,只要把它傳給最外層組件,那么內(nèi)層組件就不需要維持State它褪,全部經(jīng)父級(jí)由props往下傳即可饵骨。子組件變得異常簡(jiǎn)單。
- Redux中沒(méi)有Dispatcher的概念茫打。
Redux去除了這個(gè)Dispatcher居触,使用Store的Store.dispatch()方法來(lái)把a(bǔ)ction傳給Store,由于所有的action處理都會(huì)經(jīng)過(guò)這個(gè)Store.dispatch()方法老赤,Redux聰明地利用這一點(diǎn)轮洋,實(shí)現(xiàn)了與Koa、RubyRack類似的Middleware機(jī)制抬旺。Middleware可以讓你在dispatch action后弊予,到達(dá)Store前這一段攔截并插入代碼,可以任意操作action和Store开财。很容易實(shí)現(xiàn)靈活的日志打印汉柒、錯(cuò)誤收集、API請(qǐng)求责鳍、路由等操作竭翠。
除了以上,Redux相對(duì)Flux而言還有以下特性和優(yōu)點(diǎn):
- 文檔清晰薇搁,編碼統(tǒng)一斋扰。
- 逆天的DevTools,可以讓應(yīng)用像錄像機(jī)一樣反復(fù)錄制和重放。
目前传货,美團(tuán)外賣后端管理平臺(tái)的上單各個(gè)模塊已經(jīng)逐步替換為React+Redux開發(fā)模式屎鳍,流程的清晰為錯(cuò)誤追溯和代碼維護(hù)提供了便利,現(xiàn)實(shí)工作中也大大提高了人效问裕。