Redux的設(shè)計(jì)模式

轉(zhuǎn)自:掘金 - 隱冬
https://juejin.cn/post/6924834203129348109
React官方網(wǎng)站是這樣形容React的,A JavaScript library for building user interfaces。React實(shí)際上是一個編寫頁面的UI框架,或者說他只是一個UI的library,一個庫而已。
雖然React只是一個UI的library,不過他渲染頁面的方式卻是值得我們學(xué)習(xí)的。通過JSX動態(tài)的生成DOM來渲染頁面UI。他沒有架構(gòu),沒有模板,沒有設(shè)計(jì)模式,沒有路由,也沒有數(shù)據(jù)管理,也可以說他除了渲染UI以外什么都做不了。
但是對于一個大型的復(fù)雜的網(wǎng)站來說,設(shè)計(jì)模式和數(shù)據(jù)管理這兩個是缺一不可的,因此如果我們只使用React是沒有辦法開發(fā)大型網(wǎng)站應(yīng)用的。
比如下面這張圖,他代表的是React的組件結(jié)構(gòu),網(wǎng)站是通過組件樹的形式渲染UI的。

我們都知道React中數(shù)據(jù)流向是單向的,而且總是自上而下傳遞的,可以通過props將數(shù)據(jù)從父組件傳遞給子組件,但是假設(shè)我們需要將組件樹最底層的Banner節(jié)點(diǎn)的數(shù)據(jù)傳遞給最頂層的Index,這個時候組件之間該如何通信呢。
遵循React的單向數(shù)據(jù)傳遞原則我們是沒有辦法直接傳遞數(shù)據(jù)的不過我們可以通過函數(shù)回調(diào)的方式,通過調(diào)用父組件的函數(shù)一層一層的向上傳遞。也就是Banner調(diào)用回調(diào)將數(shù)據(jù)傳給Main,Main再通過回調(diào)將數(shù)據(jù)傳給Index。
實(shí)際上在大型的網(wǎng)站中類似這樣需要共享數(shù)據(jù)的情況非常常見,如果我們通過回調(diào)函數(shù)這樣來一層一層傳遞你會發(fā)現(xiàn)整個網(wǎng)站的代碼會變得非常惡心。基本上你的代碼就是無法維護(hù)的狀態(tài)。而且這樣處理數(shù)據(jù)的開銷是非常巨大的。一個不小心很有可能陷入無限死循環(huán)中。
所以當(dāng)我們的網(wǎng)站復(fù)雜到一定程度的時候我們就需要設(shè)計(jì)模式了,可能之前你已經(jīng)知道MVC, MVVM, MV*。但是針對React我們還可以使用一種更加符合React設(shè)計(jì)思想的架構(gòu)模式,Redux。
Redux是一種設(shè)計(jì)模式同時也是一種項(xiàng)目架構(gòu)方案,他不依賴任何庫或者任何框架,他不僅可以在React中使用甚至在Angular和Vue中也可以使用。
使用Redux架構(gòu)來說所有的組件基本不會互相通信了,數(shù)據(jù)放在一個叫做store的數(shù)據(jù)倉庫中存儲。

通過使用Redux我們可以剝離出組件中的數(shù)據(jù)(state),將所有數(shù)據(jù)統(tǒng)一存放在Redux數(shù)據(jù)(store)倉庫中,如果組件中哪一個組件需要使用到數(shù)據(jù),這個組件可以去數(shù)據(jù)倉庫中自行認(rèn)領(lǐng)有個高大上的叫法是訂閱。如果組件中對store中的數(shù)據(jù)進(jìn)行了更新那么store會向訂閱了這個數(shù)據(jù)的所有組件推送最新的數(shù)據(jù),這就是Redux的原理。
Redux就是數(shù)據(jù)倉庫,他把數(shù)據(jù)統(tǒng)一保存起來,在隔離的數(shù)據(jù)和UI的同時還處理了他們之間的關(guān)系。
使用Redux的目的是讓狀態(tài)state的變化可控可預(yù)測。雖然從原理來看Redux似乎挺簡單的但是想要了解他的工作流程就比較麻煩了。
這主要是因?yàn)樗臄?shù)據(jù)流動方式不是特別直觀,有點(diǎn)類似事件驅(qū)動的方式,我們知道事件驅(qū)動開發(fā)最困難的地方是在調(diào)試。Redux中使用了很多晦澀難懂的專業(yè)術(shù)語比如Action,Reducer,Dispatch等,了解這些名詞之前我們很難把握Redux的方向。還有就是Redux的文檔并不親民,到處都是新概念,比如說純函數(shù),flux,observable,immutable這些概念張口就來完全不去考慮別人是否可以看懂。

一般來說使用Redux都會創(chuàng)建一個用于存放數(shù)據(jù)的Store,在這個Store中有若干個Reducer,然后我們需要使用React組件來渲染UI,除此之外還會有若干個和Reducer對應(yīng)的Action指令。
Store中的Reducer組合在一起就形成了項(xiàng)目中的數(shù)據(jù)倉庫。Redux稱之為State也就是數(shù)據(jù)。React組件通過訂閱(subscribe )Store來獲得數(shù)據(jù),然后使用數(shù)據(jù)來渲染UI,UI通過顯示器顯示給用戶,用戶通過鼠標(biāo)和鍵盤與組件進(jìn)行交互,在交互中不可避免需要改變數(shù)據(jù),在React中數(shù)據(jù)的流動是單向的,所以對數(shù)據(jù)來說React組件只有讀取權(quán)限,沒有書寫權(quán)限UI組件不可以直接訪問Store修改數(shù)據(jù)。
所以UI必須向Store發(fā)送Action指令,來讓Store自己修改自己,這個指令的分發(fā)過程就叫做dispatch。Action指令到達(dá)store之后可能會經(jīng)過若干個middleware中間件進(jìn)行數(shù)據(jù)的預(yù)處理,對于數(shù)據(jù)的異步處理也是在這里進(jìn)行的,預(yù)處理過后數(shù)據(jù)就會連同action一起傳遞給reducer,reducer會按照Action中描述的指令來更新數(shù)據(jù)state,當(dāng)state更新好以后Store就會把數(shù)據(jù)推送給訂閱了自己的組件,組件會根據(jù)新的數(shù)據(jù)重新渲染UI, 用戶就能看到變化了。可以看到在實(shí)際工作中Redux架構(gòu)還是相對復(fù)雜的。
上面的描述還是比較復(fù)雜的,不過不要慌,下面我們來簡化一下這張圖,只保留幾個主要部件,通過學(xué)習(xí)簡化的流程來了解Redux。

簡化后的六層我們只保留Reducer,Store,React組件,Actions這四個部分。為了更加清晰我們這里將Reducer從Store中移了出來,實(shí)際上他們是一體的。
Store中保存的是全局?jǐn)?shù)據(jù),對于Redux項(xiàng)目來說有且只有一個Store,我們可以把它看做一個帶有推送功能的數(shù)據(jù)倉庫。我們可以借用微信的朋友圈來理解這個概念。比如你加了某個人的好友,只要這個人一發(fā)朋友圈他的狀態(tài)就會馬上推送到你。加好友就是數(shù)據(jù)訂閱,發(fā)朋友圈就是數(shù)據(jù)推送。
Reducer是幫助Store處理數(shù)據(jù)的方法,他是一個方法是一個過程是一個函數(shù)不是一個具體存在的對象,Reducer可以幫助Store初始化數(shù)據(jù),修改數(shù)據(jù),刪除數(shù)據(jù),你可能會好奇我們?yōu)槭裁匆褂肦educer這么麻煩的方式來處理數(shù)據(jù)而不是直接在Store中進(jìn)行修改,其實(shí)原因也很簡單。比如你看到你朋友的朋友圈有錯別字,你是沒辦法直接修改它的朋友圈狀態(tài)的。
任何UI級別的組件都沒有權(quán)限修改Store中的數(shù)據(jù),根據(jù)數(shù)據(jù)單向流動的原則他們是只讀不能寫的,你只能給他打電話或者發(fā)短通知他讓他來修改,他修改后會從新推送給你。給他打電話或者發(fā)短信通知他就是Action,給他打電話或者發(fā)短信通知他的這個過程就是dispatch Action消息的分發(fā)。他自己修改朋友圈的過程就是reducer。
最后他修改好之后微信會從新將消息通送給你,這就是訂閱和推送。
所以Store就是Redux中具有推送功能的數(shù)據(jù)倉庫,Reducer是Store處理數(shù)據(jù)的方法可以幫助Store實(shí)現(xiàn)數(shù)據(jù)的初始化,修改或者刪除,Actions就是數(shù)據(jù)更新的指令,他會告訴Reducer如何去處理數(shù)據(jù)所以Redux的流程其實(shí)很清晰。
首先創(chuàng)建數(shù)據(jù)倉庫Store,Reducer會同時初始化數(shù)據(jù)state。React Component會訂閱Store,Store中的數(shù)據(jù)就會被推送過來,然后渲染UI.
如果組件需要更改數(shù)據(jù)他會發(fā)送一個Action,這個過程就叫做dispatch。Action會以事件驅(qū)動的方式被Store所截獲,Store會將自己當(dāng)前的數(shù)據(jù)以及指令傳遞給Reducer,由Reducer去更新數(shù)據(jù)。Reducer更新完成以后就會向Store輸出一個新的state,Store取到新的state之后就會向訂閱了自己的React組件推送這個新的數(shù)據(jù)。然后重新再次渲染UI。這就是一個完整的Redux工作流程。
Redux是一種設(shè)計(jì)模式同時也是一種項(xiàng)目架構(gòu)方案,他不依賴任何庫或者任何框架,只是大家習(xí)慣于將Redux和React放在一起使用。這里我們介紹一下Redux的使用,為了避免混淆我們不使用任何框架。
首先你可以通過npm在項(xiàng)目中安裝redux插件,前面說過Store就是保存數(shù)據(jù)的地方,整個應(yīng)用只能有一個Store, Redux提供createStore這個函數(shù),用來生成Store。
import { createStore } from 'redux'
const store = createStore(fn);
這里createStore需要接收一個函數(shù),這個函數(shù)就是用來處理action操作的也就是我們之前說的Reducer,所以他需要接收action參數(shù),因?yàn)樗菐椭鶶tore處理數(shù)據(jù)的,所以也需要接收源數(shù)據(jù),返回值是更新后的數(shù)據(jù)。
const fn = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
在需要使用數(shù)據(jù)的位置我們可以通過getState來獲取數(shù)據(jù),通過subscribe訂閱來監(jiān)聽數(shù)據(jù)的變化,因?yàn)镽edux是一種發(fā)布訂閱模式,只有監(jiān)聽才會獲取到。
store.subscribe(() => {
const state = store.getState();
}))
需要更數(shù)據(jù)時,需要使用dispatch配合action來分發(fā)。我們約定action需要是一個擁有type屬性的對象,type來表示要操作的類型,如果傳遞參數(shù)我們一般將參數(shù)放在payload屬性中。
const action = {
type: 'INCREMENT',
payload: '參數(shù)',
};
store.dispatch(action);
這樣我們當(dāng)調(diào)用store.dispatch時,Redux會將action傳遞給Reducer,Reducer通過自身的邏輯處理返回新的state,然后Redux記錄這個新的state并且推送消息給訂閱了自己的組件。也就是會觸發(fā)subscribe中傳入的函數(shù)。函數(shù)中可以通過store.getState()獲得新的state值,完成頁面更新。
假設(shè)我們頁面中有一個button按鈕和一個div元素,這個元素用來展示一個數(shù)字,初始值為0,當(dāng)我們點(diǎn)擊button按鈕的時候讓div中顯示的數(shù)字增加。
<div id="count"></div>
<button id="button">按鈕</button>
js代碼如下, 我們首先定義reducer,在里面判斷如果type為INCREMENT就讓state+1,然后通過createStore創(chuàng)建store傳入處理函數(shù)reducer。
接著訂閱state,當(dāng)state變更時獲取頁面div元素更新div的內(nèi)容為state的值。
最后點(diǎn)擊按鈕的時候我們通過dispatch來分發(fā)action。
var reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
default:
return state
}
}
var store = createStore(reducer);
store.subscribe(() => {
document.querySelector('#count').innerHTML = store.getState();
});
document.querySelector('#button').addEventListener('click', function () {
store.dispatch({type: 'INCREMENT'});
});
這就是Redux的一個基本使用過程,你懂了么?
那具體什么時候需要使用到Redux呢?
組件需要共享數(shù)據(jù)或者共享狀態(tài)(state)的時候;
某一個組件在任何地方都需要被隨時訪問的時候。
某一個組件需要改變另一個組件狀態(tài)的時候。
網(wǎng)站支持國際化語言切換,登錄數(shù)據(jù)共享的情況下。
滿足上面一種或幾種情況建議使用redux,如果你還在考慮項(xiàng)目要不要使用redux我給的建議就是不要。技術(shù)是為了服務(wù)業(yè)務(wù)。為了避免設(shè)計(jì)的頭重腳輕,建議只有在需要的時候才引入新概念,切忌為了使用而使用。
- EOF -
