Redux 是一個(gè)混合產(chǎn)物。它和一些設(shè)計(jì)模式及技術(shù)相似,但也有不同之處。讓我們來(lái)探索一下這些相似與不同。
Redux 可以被看作 Flux 的一種實(shí)現(xiàn)嗎?是,也可以說(shuō) 不是。
(不用擔(dān)心,Flux 的作者認(rèn)可它,如果你好奇這一點(diǎn)。)
Redux 是從很多有質(zhì)量的 Flux 的實(shí)現(xiàn)中產(chǎn)生的靈感。和 Flux 一樣,Redux 規(guī)定,將模型的更新邏輯全部集中于一個(gè)特定的層(Flux 里的 store,Redux 里的 reducers)。還想告訴你的是,它不使用應(yīng)用代碼直接變動(dòng)數(shù)據(jù),而用一個(gè)叫作 “action” 的普通對(duì)象來(lái)對(duì)變化進(jìn)行描述。
而不同于 Flux ,Redux 沒(méi)有 dispatcher 的概念。原因是它依賴純函數(shù)來(lái)替代事件處理器。純函數(shù)構(gòu)建簡(jiǎn)單,也不需額外的實(shí)體來(lái)管理它們。你可以將點(diǎn)這看作這兩個(gè)框架的差異或細(xì)節(jié)實(shí)現(xiàn),取決于你怎么看 Flux。Flux 常常被表述為 (state, action) => state
。從這個(gè)意義上說(shuō),Redux 無(wú)疑是 Flux 架構(gòu)的實(shí)現(xiàn),且得益于其純函數(shù)而更為簡(jiǎn)單。
和 Flux 的另一個(gè)重要區(qū)別,是 Redux 設(shè)想你永遠(yuǎn)不會(huì)變動(dòng)你的數(shù)據(jù)。你可以很好地使用普通對(duì)象和數(shù)組來(lái)管理 state ,而不是在多個(gè) reducer 里變動(dòng)數(shù)據(jù),這會(huì)讓你深感挫折。正確的方式是,你應(yīng)該在 reducer 中返回一個(gè)新對(duì)象來(lái)更新 state, 同時(shí)配合 ES7 所提議的 object spread 語(yǔ)法 和 Babel,或者一些庫(kù),如 Immutable ,這種做法簡(jiǎn)單易行。
雖然出于性能方面的考慮,寫(xiě)不純的 reducer 來(lái)變動(dòng)數(shù)據(jù)在技術(shù)上是可行的,但我們并不鼓勵(lì)這么做。不純的 reducer 會(huì)使一些開(kāi)發(fā)特性,如時(shí)間旅行、記錄/回放或熱加載不可實(shí)現(xiàn)。此外,在大部分實(shí)際應(yīng)用中,這種數(shù)據(jù)不可變動(dòng)的特性并不會(huì)帶來(lái)性能問(wèn)題,就像 Om 所表現(xiàn)的,即使對(duì)象分配失敗,仍可以防止昂貴的重渲染和重計(jì)算。而得益于 reducer 的純度,應(yīng)用內(nèi)的變化更是一目了然。
Elm 是一種函數(shù)式編程語(yǔ)言,由 Evan Czaplicki 受 Haskell 語(yǔ)言的啟發(fā)開(kāi)發(fā)。它執(zhí)行一種 “model view update” 的架構(gòu) ,更新遵循 (state, action) => state
的規(guī)則。 從技術(shù)上說(shuō),Elm 的 “dater” 等同于 Redux 里的 reducer。
不同于 Redux,Elm 是一門語(yǔ)言,因此它在執(zhí)行純度,靜態(tài)類型,不可變動(dòng)性,action 和模式匹配等方面更具優(yōu)勢(shì)。即使你不打算使用 Elm,也可以讀一讀 Elm 的架構(gòu),嘗試一把。基于此,有一個(gè)有趣的使用 JavaScript 庫(kù)實(shí)現(xiàn)類似想法 的項(xiàng)目。我們能看到 Redux 從中取得的靈感! 為了更接近 Elm 的靜態(tài)類型,它使用了一個(gè)類似 Flow 的漸進(jìn)類型解決方案 。
Immutable 是一個(gè)可實(shí)現(xiàn)不可變數(shù)據(jù)結(jié)構(gòu)的 JavaScript 庫(kù)。它十分高性能,并擁有常用的 JavaScript API。
Immutable 及類似的庫(kù)都與 Redux 對(duì)接良好。盡可隨意地一起使用!
Redux 并不在意你 如何 存儲(chǔ) state,state 可以是普通對(duì)象,可以是不可變對(duì)象,或者其它類型。 為了從 server 端寫(xiě)同構(gòu)應(yīng)用或融合它們的 state ,你可能要用到序列化或反序列化的機(jī)制。但除此以外,你可以使用任何數(shù)據(jù)存儲(chǔ)的庫(kù),只要它支持?jǐn)?shù)據(jù)的不可變動(dòng)性。舉例說(shuō)明,對(duì)于 Redux state ,Backbone 并無(wú)意義,因?yàn)?Backbone model 是可變的。
注意,即便具有不可變特性的庫(kù)支持 cursor,也不應(yīng)在 Redux 的應(yīng)用中使用。整個(gè) state tree 應(yīng)被視為只讀,并需通過(guò) Redux 來(lái)更新 state 和訂閱更新。因此,通過(guò) cursor 來(lái)改寫(xiě),對(duì) Redux 來(lái)說(shuō)沒(méi)有意義。而如果只是想用 cursor 把 state tree 從 UI tree 解耦并逐步細(xì)化 cursor,應(yīng)使用 selector 來(lái)替代。 Selector 是可組合的 getter 函數(shù)組。具體可參考 reselect,這是一個(gè)優(yōu)秀、簡(jiǎn)潔的可組合 selector 的實(shí)現(xiàn)。
Baobab 是另一個(gè)流行的庫(kù),實(shí)現(xiàn)了數(shù)據(jù)不可變特性 API 用以更新純 JavaScript 對(duì)象。你當(dāng)然可以在 Redux 中使用它,但兩者一起使用并沒(méi)有什么優(yōu)勢(shì)。
Baobab 所提供的大部分功能都與使用 cursors 更新數(shù)據(jù)相關(guān),而 Redux 更新數(shù)據(jù)的唯一方法是分發(fā)一個(gè) action 。可見(jiàn),兩者用不同方法,解決的卻是同樣的問(wèn)題,相互并無(wú)增益。
不同于 Immutable ,Baobab 在引擎下還不能現(xiàn)實(shí)任何特別有效的數(shù)據(jù)結(jié)構(gòu),同時(shí)使用 Baobab 和 Redux 并無(wú)裨益。這種情形下,使用普通對(duì)象會(huì)更簡(jiǎn)便。
Reactive Extensions (和它們正在進(jìn)行的 現(xiàn)代化重寫(xiě)) 是管理復(fù)雜異步應(yīng)用非常優(yōu)秀的方案。以外,還有致力于構(gòu)建人機(jī)交互并將其視作相互依賴的可觀測(cè)變量的庫(kù)。
同時(shí)使用它和 Redux 有意義么? 當(dāng)然! 它們配合得很好。將 Redux store 視作可觀察變量非常簡(jiǎn)便,例如:
function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = store.subscribe(() => onNext(store.getState()));
onNext(store.getState());
return { dispose };
}
}
}
使用類似方法,你可以組合不同的異步流,將其轉(zhuǎn)化為 action ,再提交到 store.dispatch()
。
問(wèn)題在于: 在已經(jīng)使用了 Rx 的情況下,你真的需要 Redux 嗎? 不一定。通過(guò) Rx 重新實(shí)現(xiàn) Redux 并不難。有人說(shuō)僅需使用一兩句的 .scan()
方法即可。這種做法說(shuō)不定不錯(cuò)!
如果你仍有疑慮,可以去查看 Redux 的源代碼 (并不多) 以及生態(tài)系統(tǒng) (例如開(kāi)發(fā)者工具)。如果你無(wú)意于此,仍堅(jiān)持使用交互數(shù)據(jù)流,可以去探索一下 Cycle 這樣的庫(kù),或把它合并到 Redux 中。記得告訴我們它運(yùn)作得如何!
更多建議: