原文連結: www.smashingmagazine.com/2018/07/red…
推薦理由: 插圖大愛 沒有空洞的概念 也沒有海量的程式碼!
內容概要: 你是否知道 Redux 的真正威力遠不止狀態管理嗎?你是否想要了解 Redux 的工作原理?讓我們來深入介紹 Redux 到底能做些什麼?為什麼它是這樣設計的?它的缺點有哪些?以及它與設計有哪些關聯?
你是否聽說過 Redux ?它到底是什麼?不允許 Google !
- “花哨的後端技術。”
- “我聽說過,但不知道是幹什麼用的。好像是一個 React 框架?”
- “是一種在React 應用中儲存管理狀態的更好方式。”
這個問題我曾問過不下於40個設計師。上面列出的是他們的經典回答。他們中不少人都知道 Redux 是和 React 一起工作的,並且它的職責是“狀態管理”。
但是你可知道這個“狀態管理”的真正含義嗎?你是否知道 Redux 的真正威力遠不止狀態管理嗎?你是否知道 Redux 並非一定要搭配 React 來使用?你是否想要加入團隊談論(至少是午餐討論),關於是否使用 Redux ?你是否想要了解 Redux 的工作原理?
本文的目的就是讓你對 Redux 有更全面的認知: 它能做什麼?為什麼它要這樣設計?何時使用它?以及它與設計有哪些關聯?
我的目標是幫助像你一樣的設計師。儘管你可能連一行程式碼都沒寫過,不過我認為還是可以理解 Redux的,並能從中受益和享受樂趣。貫穿全文的只有樸實的語言及有趣的塗鴉,沒有任何程式碼及高談闊論。
準備好了嗎?
什麼是 Redux ?
從大局來看的話,Redux 是一種讓開發者的工作更為輕鬆的工具。正如你所聽過的,它的職責是“狀態管理”。稍後我將會解釋什麼是狀態管理。此刻,我只能想讓你看下面這張圖:
? Redux 用來管理狀態,但在狀態管理的背後,還有一些隱藏的能力 (Beebee 作圖)
為什麼要關心 Redux ?
Redux 所完成的工作,更多的是應用的內部運轉,而不是外觀介面。它是一個複雜的工具,學習曲線很陡。這是否意味著作為設計師的我們應該對它避而遠之呢?
不。我覺得我們應該擁抱它。汽車設計師應該理解引擎是做什麼的,你說是嗎?要想成功地設計應用的介面,設計師應該清楚地瞭解應用背後的工作原理。我們應該瞭解它能做什麼,理解開發者為什麼要使用它,以及知道它的優點和侷限性。
“設計不僅僅是外觀感受。設計關乎於工作原理。”
— 史蒂夫·賈伯斯
Redux 可以做什麼?
許多人在 React 應用中使用 Redux 來管理狀態。這是最常見的用法,Redux 解決了 React 使用過程中的一些痛點。
但是,很快你就會感受到 Redux 的威力遠不止於此。我們首先來介紹到底什麼才是狀態管理。
狀態管理
如果你不確定這個“狀態”到底表示什麼,那我們用個更通俗的術語“資料”來進行替代。狀態是隨時間流逝而產生變化的資料。狀態決定了展示給使用者的介面。
到底什麼才是狀態管理?通常來說,在一個應用中資料管理大致包括三方面的工作:
比如我們要做一個 Dribbble 的作品頁面。在作業頁面上我們想要展示的資料有哪些?其中包括作者的頭像照片、名稱、動態 GIF 圖片、點贊數量、評論,以及等等。
? Dribbble 作品頁面的資料
首先,我們需要從雲端伺服器拉取這些資料並將其儲存起來。接下來需要實際顯示資料。我們需要將資料拆分開,然後分配給與之對應的 UI 元素,這些 UI 元素正是我們在瀏覽器中實際所見的。例如,我們將頭像照片的 URL 分配給 img
標籤的 src
屬性:
<img src='https://url/to/profile_photo'>
複製程式碼
最後,我們需要處理資料的變更。例如,如果使用者為作品新增了一條評論或點贊,我們需要更新相對應的 HTML 元素。
在前端開發過程中,協調上述三方面的工作是一項大工程,React 對此有著不同程度的支援。有些時候,React 內建的功能就能很好的完成任務。但隨著應用變得愈發複雜,單單依靠 React 進行狀態管理會變得如履薄冰。這正是許多人開始使用 Redux 的初衷。
獲取並儲存資料
在 React 中,我們將 UI 拆分成元件。每個元件又可以拆分成更小的元件。(參見 “圖解 React”)
? Dribbble 的作品頁面拆分成元件
頁面的結構是這樣的,那麼在渲染頁面之前我們何時去獲取資料呢?又將資料儲存在何處呢?
想象一下,每個元件裡都住著一位大廚。從伺服器獲取資料就好比是採購所需的所有原材料以準備佳餚。
一個簡單的辦法是在需要的時候才去獲取資料,並將其就地儲存起來。這樣就好比每個大廚都驅車前往郊外的農場來採購蔬菜和肉類。
? 簡單方式: 每個元件各自獲取自己所需要的資料 (Beebee 作圖)
這種方式是一種浪費。有多少個元件我們就得請求伺服器多少次,即使是相同的資料。大廚們會浪費大量的汽油和時間在往返的路上。
使用 Redux ,我們只獲取資料一次,並將資料儲存在一箇中心區域,通常稱之為 “store” 。這樣資料對於任何元件來說都可以隨時使用。這就像附近有一家超市,我們的大廚們可以在那裡買到所有食材。超市會派卡車去農場大批量地運回蔬菜和肉類。這比每個大廚都親自去農場採購要有效率得多!
store 還是唯一的資料來源。元件通常從 store 中獲取資料,而不是其他地方。這使得 UI 保持高度統一。
? Redux 將資料集中地儲存起來 (Beebee 作圖)
將資料分配給 UI 元素
如果單單使用 React 的話,實際上有一種更好的方式來獲取並儲存資料。我們可以請求非常善良的大廚 Shotwell 來為所有的廚師朋友們採購。他可以驅車前往農場將貨物全部運回來。在 React 的世界中就是從一個容器型元件來獲取資料,例如,Dribbble 示例中的 “Shot” 元件就是容器型元件,可以使用它來作為單一資料來源。
? 從根元件獲取資料 (Beebee 作圖)
這種方式要比每個元件單獨去獲取資料要高效得多。但是大廚 Shotwell 如何將食材分給其他大廚們呢?換句話說,如何將資料傳遞給實際負責渲染 HTML 元素的元件?將資料從外層元件傳遞給內層元件就好比是接力賽中的接力棒,一層層地傳遞下去直到抵達目的地。
舉個例子,作者頭像的 URL 需要從 “Shot” 傳出,然後傳到 “ShotDetail”,再到 “Title” ,最後才能傳給 <img>
標籤。如果每個大廚都住在公寓裡的話,應該就如下圖中展示的一般:
? 通過 props 將資料傳遞給目標元件 (Beebee 作圖)
要將資料傳遞給目標元件,我們需要使用傳遞路徑上的所有元件,無論這些元件是否需要使用此資料。如果這個公寓是一個摩天大廈,那就太煩躁了!
如果超市送貨上門呢?使用 Redux 的話,我們可以將任意資料提取至任意元件而壓根不會影響到其他元件,就像這樣:
更準確地說,實際上是另一個叫做
react-redux
的庫將資料提供給元件的,而並非 Redux 本身。但因為 react-redux 本身只是個連線庫,並且開發者通常一起使用 Redux 和 react-redux ,因此我認為將它當做是 Redux 的好處之一是並無不妥。
? 使用 Redux 將資料直接提取至目標元件 (Beebee 作圖)
注意: 在 React 的16.3版本中,提供了一個新的 “context” API ,它的提取資料功能幾乎與 Redux 是相同的。如果你的團隊使用 Redux 只為提取資料的話,不妨認真考慮將 React 版本升至16.3!想了解更多詳情,請參見 官方文件 (溫馨提示: 文件中有大量程式碼) 。
改變資料
有時候,在應用中更新資料的邏輯可能會相當複雜。它可能涉及到多個相互依賴的步驟。在更新應用的狀態之前,我們可能需要等待多個伺服器的響應。我們還可能需要根據不同條件、在多個事件點更新狀態內的多處資料。
如果我們沒有一個好的結構來實現所有這些邏輯,那將是毀滅性的,程式碼將難以理解與維護。
Redux 可以讓我們進行分治。它提供了一種標準方式來將資料更新邏輯拆分成眾多小塊的 “reducers” 。這些 reducers 可以在一起協調工作,以完成複雜的動作。
? 將複雜邏輯拆分成 reducer (Beebee 作圖)
沒事可以多關注一下 React 的開發進展。就像 “context” API ,在 React 未來的版本中還可能會出現一個新的 “setState” API 。它可以將目前複雜的更新邏輯拆分成一個個小塊。一旦這個新的 API 推出的話,很可能屆時將不再需要 Redux 來管理狀態。
Redux 的真正威力
到目前為止,Redux 看上去只是 React 的輔助工具。開發者使用它來解決 React 的某些痛點。但 React 正在快速著手解決這些問題!事實上,Redux 的作者 Dan Abramov 在幾年前已經加盟 Facebook 的 React 核心團隊。他們一直致力於提升 React 的開發體驗: context API (16.3版本釋出)、更好的資料獲取 API (詳情請見 Dan Abramov 於2018年2月的演講)、更好的 setState API,等等。
這是否意味著 Redux 將被淘汰?
你猜呢?我還未向你展示 Redux 的真正威力呢!
? Redux 的威力遠不止狀態管理 (Beebee 作圖)
Redux 強制開發者遵循幾個原則,正是這些原則為 Redux 帶來了強大的功能(這正是約束的力量!):
- 所有資料(應用的狀態)都必須能夠以文字的形式進行描述。也就是說,你需要能夠用筆將所有資料在紙上寫出來。(譯者注: 這裡的資料應該指資料結構和資料型別兩方面)
- 每個動作(改變資料的操作)都必須能夠以文字的形式進行描述。你必須在改變資料之前將其寫出來。沒有動作就無法改變資料。在 Redux 的術語中這稱之為 “派發 (dispatching) 動作”。
- 改變資料的程式碼必須能夠像數學公式一樣執行。給定相同的輸入,必須返回同樣的結果。無論計算多少次,4 的平方永遠都是 16 。
當你遵循上述原則來開發應用的話,不可思議的事情就來了。Redux 將開啟許多很酷的特性,這些特性使用其他技術很難實現,或者實現起來成本很高。下面是一些例子。
我從 Dan Abramov 文章 “You Might Not Need Redux” 和 “React Beginner Question Thread.” 中收集了一些示例。
撤消、重做
流行的撤消/重做功能需要系統級的規劃。因為撤消/重做需要記錄並回放應用中發生的每次資料變化,必須從一開始就在架構層面中考慮它。如果是事後才做,就需要修改大量的檔案,這將導致層出不窮的 bugs。
? 撤消、重做 (Beebee 作圖)
正因為 Redux 需要每個動作都以文字的形式進行描述,所以可以說是天生就支援撤消/重做。這個文件中介紹瞭如何使用 Redux 來實現撤消/重做。
協作環境
如果你開發的應用類似於 Google Docs ,可以多人協作來完成複雜任務,可以考慮使用 Redux 。它能夠為你完成大量繁重的工作。
? Google Docs (Beebee 作圖)
Redux 使得通過網路來傳送當前使用者正在做的事變得很簡單。接收到另一個使用者在另一臺機器上執行的操作後,重放另一個使用者所做的更改,並與本地正在發生的動作合併起來是很容易的。
Optimistic UI
Optimistic UI 是一種提升應用使用者體驗的方式。它可以使得執行在慢網速上的應用也能快速響應使用者的操作。在需要實時響應的應用中,這是一種流行的策略,例如第一人稱射擊遊戲。
? Optimistic UI (Beebee 作圖)
舉個簡單例子,在 Twitter 應用中,當你點贊時其實是需要請求伺服器來做一些檢查的,例如當前推文是否存在。Optimistic UI 的做法不是傳統的轉圈等待幾秒,然後顯示結果,而是選擇欺騙使用者!它事先假定所有請求都是成功的,當使用者點贊時直接+1。
? Twitter 點贊 (Beebee 作圖)
這種方式有效的原因在於大多數時候請求都是正常的。當請求失敗時,應用只需回滾至前一個 UI 狀態即可,並使用伺服器響應的實際結果,例如顯示錯誤資訊。
如同撤消/重做一樣,Redux 也支援 Optimistic UI 。它使得一切都變得簡單起來,比如紀錄、重放和當請求失敗時進行回滾。
狀態持久化和初始狀態載入
Redux 使得將應用中發生的一切紀錄並儲存下來變得非常簡單。就算後面電腦重啟,應用也能夠輕鬆載入所有資料,並從完全相同的位置繼續執行,就好像它從來沒有被中斷過一樣。
? 儲存/載入遊戲進度 (Beebee 作圖)
使用 Redux 開發遊戲的話,只需少量程式碼便能夠儲存/載入遊戲進度,而無需改變遊戲本身的程式碼。
真正可擴充套件的系統
使用 Redux ,你必須 “dispatch” 動作才能更新應用中的資料。這一限制使得我們可以幾乎偵測和擴充套件應用中發生的一切。
你可以構建真正可擴充套件的應用,其中每個功能都可以由使用者來自定義。例如,參考 Hyper ,這是一個使用 Redux 開發的終端應用。“hyperpower” 外掛增加了游標的閃光點,並可以使視窗抖動。你是否喜歡這種 “wow” 模式呢?(或許這功能並沒有什麼用,但卻是足夠吸人眼球)
? 終端應用 Hyper 中的 “wow” 模式 (Beebee 作圖)
時間旅行除錯
當除錯應用時能夠進行時間旅行會是怎樣一種體驗?執行應用的過程中,隨意倒退或前進幾次以找到 bug 發生的確切位置,修復 bug 後重放以確認是否修復。
Redux 讓開發者夢想成真。Redux 開發者工具可以使開發者通過拖拽滑動條來操縱應用的進度,就像 Youtube 視訊一般。
它是如何工作的呢?還記得 Redux 的三大原則嗎?它們正是祕訣所在。
? 在 Redux 開發者工具進行時間旅行 (Beebee 作圖)
自動反饋 Bug
想象一下,使用者發現應用中存在問題並想進行反饋。她煞費苦心地回憶和描述她所做過的一切。然後開發人員嘗試去手動執行這些步驟以檢視 bug 是否復現。使用者的反饋很可能是模稜兩可的,於是開發人員很難找到 bug 出現的原因。
如果是這樣呢,當使用者點選 “反饋問題” 按鈕時,系統會自動地將使用者本地的狀態傳送給開發人員。開發人員隨即點選 “重發 bug” 按鈕便可檢視 bug 究竟是如何產生的。Bug 隨即被修復,大家都很開心!
Redux Bug Reporter 就是這樣玩的。它的工作原理呢?Redux 的限制條件讓一切變成可能。
? 自動反饋 Bug (Beebee 作圖)
Redux 的缺點
Redux 的三大原則其實是一把雙刃劍。它開啟強大功能的同時也不可避免地帶來一些副作用。
陡峭的學習曲線
Redux 的學習曲線相當陡峭,需要時間去理解、記憶和熟悉它的模式。如果你完全不會 Redux 和 React ,不推薦你兩者同時學習。
“樣板” 程式碼
在大多數情況下,使用 Redux 就意味著要多寫很多程式碼。通常需要編寫多個檔案才能讓一個小功能執行起來。開發者一直都在抱怨使用 Redux 時所編寫的“樣板”程式碼。
我知道,這似乎有點自相矛盾。明明才說過 **Redux可以省程式碼,怎麼現在又來說Redux費程式碼?**這就有點類似於使用洗碗機。首先,你得花時間仔細地排列盤子。直到完成洗碗你才感受到洗碗機的好處,它節省了洗碗、餐具消毒等方面的時間。所以需要你來決定這個準備時間是否值得!
效能損耗
Redux 的三大原則會對效能產生一些影響。每當資料發生變化時,它會增加一點效能開銷。在絕大多數情況下,這不是什麼大問題,效能損耗也不明顯。但是,當 store 中有大量資料並且資料變化頻率非常高的話(例如使用者在移動裝置上頻繁地打字),UI 可能會變得卡頓。
加分項: Redux 不只是為 React 而生
一種常見的誤解就是 Redux 只是為 React 提供的,如果離開了 React ,Redux 將毫無用處。確實,正如我們之前一直所討論的,Redux 解決了 React 的一些痛點。React 是最最常見的 Redux 用例。
但是事實上,Redux 可以和任何前端框架一起使用,比如 Angular、Ember.js,甚至是 jQuery 或原生 JavaScript 。試試 Google 一下,你會發現 這個、這個、這個,甚至是這個。Redux 的思想可以應用於任何地方 !
隨著你越來越廣泛地使用 Redux ,你可以在多種場景下享受它帶來的好處,而不僅僅是在 React 應用中。
? Redux 可以搭配其他前端框架一起使用 (Beebee 作圖) ? Redux 可以搭配其他前端框架一起使用 (Beebee 作圖)
總結
作為一種工具,Redux 的使用效果是兩方面的。它開啟強大功能的同時也帶來了一些不可避免的缺點。一個開發團隊的職責就是進行評估,看如何進行取捨並作出明智的選擇。
作為設計師,如果我們能夠理解 Redux 的優缺點,我們就能夠從設計的角度為此決策作出貢獻。舉個例子,或許我們設計出的 UI 能夠緩解潛在的效能影響?也許我們可以提倡使用撤消/重做功能來替代大量的確認對話方塊?或許我們可以提倡 optimistic UI ,因為它能夠以相對較低的代價來提升使用者體驗。
理解技術的好處與侷限性,並作出相應的設計。這正是我對斯蒂夫·賈伯斯的名言 “設計關乎於工作原理。” 的解讀。