從新設計 Redux

Redux 學習起來很困難?寫起代碼來很囉嗦?
一塊兒來看看 Rematch 的做者對 Redux 的思考和簡化吧~前端

原文:《Redesigning Redux》, Shawn McKaygit

都過了這麼多年了,狀態管理的問題難道不該該早就被解決了麼?
我的直覺,開發者彷佛都認可一個潛規則,那就是狀態管理問題彷佛比想象的更復雜。在本文,咱們將一塊兒探討你可能已經遇到的問題:github

  • 咱們真的須要一個狀態管理庫麼?
  • Redux 的流行是否實至名歸?爲何?
  • 咱們是否可以提出更好的狀態管理方案?若是是,怎麼作?

真的須要狀態管理庫?

前端開發並不只僅是左右移動像素點。開發的真正藝術是掌握如何管理狀態。
簡單粗暴的答案是:狀態管理是複雜的,可是並不是那麼複雜。編程

以基於組件的視圖框架/庫爲例,好比 React,讓咱們來看看咱們有哪些選擇:redux

State Options

1. 組件狀態

存在於單個組件內部的狀態。在 React 中,就是指使用 setState 來更新的 state數組

2. 相關狀態

父組件傳遞給子組件的狀態。在 React 中,就是指經過屬性傳遞給子組件的 props架構

3. 供給狀態

保存在根提供者中、經過組件樹傳遞給消費者的狀態。在 React 中,對應於 context API框架

大多數的狀態都是存在於視圖中的,由於它是用來反映用戶界面的。那麼,對於反映底層數據和邏輯的其它狀態,又屬於誰呢?異步

若是把全部的狀態都填塞到視圖中,那麼就嚴重違背了關注點分離原則。它會把你緊緊地綁在一個 JS 視圖庫中,使得代碼難以測試。更有甚者,可能會爲你帶來大麻煩,由於你必須持續不斷的思考和調整狀態的存儲之處。async

因爲架構設計的改變,狀態管理會隨之變得複雜,而且一般很難判斷哪些組件須要哪些狀態。最簡單的作法是,在根組件上包含全部狀態。若是真要這麼作的話,那麼選用下一種方式會更好。

4. 外部狀態

狀態是能夠從視圖庫中移出來的,而後可使用提供者/消費者模式把狀態從新鏈接回視圖庫。

Redux 也許是最流行的狀態管理庫。它在過去的 2 年時間裏取得了巨大的流行度。那麼,爲何一個簡單的庫會得到如此之多的關注呢?

是由於它的性能更高麼?其實並非。實際上,它反而讓每個必須處理的回調變得更慢了些。

那是由於它更簡單?也決定不是。

論簡單的話,那麼純 JS 纔是。正如 TJ 所說:

TJ Twitter

那爲何你們不直接使用 global.state = {} 呢?

爲何使用 Redux

本質上,Redux 跟 TJ 所說的是同一件事,只不過 Redux 封裝了一些管道工具而已。

Redux Store Pipeline

在 Redux 中,咱們不能直接修改狀態。修改狀態的惟一方式是分發(Dispatch)一個動做(Action)到管道中,管道會自動根據動做去更新狀態。

從上圖能夠看到,管道的中有 2 個監聽器集合:中間件(Middleware)和訂閱(Subscription)。中間件一些是監聽動做的函數,用來處理相似日誌記錄、開發工具和發起服務請求等功能。訂閱則是一些用來廣播狀態變動的函數。

最後,合成器(Reducer)函數負責把狀態變動拆分紅更小、更模塊化、更容易管理的代碼塊。

和使用一個全局對象相比,Redux 確實簡化了開發過程。

綜上,咱們能夠把 Redux 當作是一個全局對象,該對象不只提供了狀態更新前/後鉤子,並且可以以簡單的方式合成新狀態。

不以爲 Redux 過於複雜麼?

的確過於複雜。在平時的開發過程當中,有一些不能否認的跡象,能夠用來判斷框架 API 是否須要改進。這些跡象能夠概括爲下面這個公式:

Quality of API

其中,節省的時間是指使用該框架來開發所消耗的時間,學習的時間則是閱讀框架文檔、學習教程和掌握新概念的總時間。

Redux 自己是個精簡的庫,可是其學習曲線卻很陡峭。雖然有很多開發者可以克服深刻學習函數式編程的困難並從 Redux 獲益良多,可是也有不少開發者望而卻步,寧願從新使用 jQuery。

在 jQuery 中,你並不須要理解什麼是 「comonad」,也不必理解經過函數組合來管理狀態。

任何框架或者庫的目的都應該是把複雜的事物抽象得更加簡單。

固然我這麼說並非想指責 Redux 的做者 Dan Abramov 。Redux 在其剛誕生初期就已經變得很是流行,沒有足夠的時間來精雕細琢。

  • 咱們怎麼能隨便重構一個已經被成千上萬開發者使用的庫呢?
  • 咱們又怎麼能合理發佈會影響到不可勝數項目的重大變動呢?

沒人能夠。可是咱們能夠提供更好的支持,好比完善文檔、推廣視頻教程和擴大社區等。在這些方面,Dan Abramov 確實作得很棒。

又或者,還有另外一種方式能夠改變現狀。

從新設計 Redux

在我看來,重寫 Redux 是有其必要性的,至少有如下 6 個方面能夠改進得更友好。

1. 初始化過程

讓咱們來看看一個基本的 Redux 初始化過程,以下圖左邊所示:

setup

不少開發者走到這裏一步就開始止步了,簡直是一看三不知。什麼是 chunkcompose 又是什麼鬼?一個函數還能這麼使用?

若是 Redux 是基於配置而不是函數組合的話,那麼像右邊那樣的初始化過程明顯看起來更加合理。

2. 簡化狀態合成器

Redux 中的狀態合成器可以使用一個 switch 來代替多個沒必要要的 switch

reducers

假如狀態合成器是根據動做的類型來匹配的,那麼咱們能夠用逆向思惟,把合成器變成一個接受 stateaction 兩個參數的純函數。也許還能夠更簡單些,咱們能夠把動做規劃化,而且只傳遞狀態和一個數據載荷。

3. 使用 Async/Await 代替 Thunks

在 Redux 中,Thunks最通用的作法就是用來建立異步動做。從多角度來看,這種用法更像是一個聰明的黑客所採用的用法,而不是一種官方推薦的用法。咱們一步一步來看:

  1. 首先分發了一個動做,然而實際上倒是一個函數而不是指望的對象
  2. Thunk 中間件檢查每個動做,看它是不是一個函數
  3. 若是是函數,那麼中間件調用該函數,同時把 dispatchgetState 方法傳參進去

真的須要這樣麼?把一個簡單的動做在運行時識別爲對象、函數甚至是 Promise,這難道不是糟糕的實踐麼?

async & await

如上圖右邊所示,難道咱們就不能只使用 async/await ?

4. 兩種類型的動做

若是咱們認真想一想的話,確實有兩種類型的動做:

  1. 合成器動做(Reducer action): 觸發合成器而後改變狀態
  2. 反作用動做(Effect action):觸發一個異步動做。這可能也稱爲合成器動做,可是異步函數其實並無直接改變任何狀態。

所以,把這兩種類型的動做區分開來會更有用,同時也不容易與 Thunks 搞混。

5. 再也不須要定義動做類型變量

爲何咱們的標準實踐要把動做生成器和狀態合成器區分開來呢?可否只用其中一個呢?改變其中一個又是否會影響到另外一個?

在我看來,動做生成器和狀態合成器就是硬幣的兩個面。

const ACTION_ONE = 'ACTION_ONE' 能夠說是把動做生成器和狀態合成器分開的冗餘產物。若是咱們把這二者合二爲一,那麼就不會有那麼多文件專門導出這些類型字符串了。

6. 合二爲一的合成器

按照使用方式,把 Redux 中所涉及的概念進行合併分組,那麼咱們能夠得出下面這個更簡單的模式。

simpler pattern

在這種模式中,狀態合成器是能夠自動肯定與之對應的動做生成器。由於,此時狀態合成器能夠自動變成動做生成器。

經過簡單的命名約定,如下行爲都會變得可預測:

  1. 若是狀態合成器命名爲 increment,那麼動做類型就是 increment。更好的作法是加上命名空間: count/increment
  2. 每一個動做都經過 payload 屬性來傳遞數據。

action creator

這樣的話,對於 count.increment,咱們就能夠自動的從狀態合成器推導出動做生成器。

好消息:更棒的 Redux

以上的通電就是咱們建立 Rematch 的緣由。

rematch

Rematch 對 Redux 進行了封裝,提供更簡單的 API,但又不失任何可配置性的特色。

rematch model

下面是一個完整的使用例子:

rematch example

我已經把 Rematch 應用在生成環境中有好幾個月了。做爲小白鼠,個人體驗是:

狀態管理從未變得如此簡單、高效。

Redux 並無被拋棄,並且也不該該被拋棄。
只是,咱們應該以更低的學習成本,更少的樣板代碼和更少的認知成本,來擁抱 Redux 背後的簡單哲學。

趕忙試一試 Rematch 吧!萬一一不當心你就愛上它了呢?

相關文章
相關標籤/搜索