【第1329期】從設計師的角度看 Redux

前端早讀課2018-07-12 05:36:36

前言

今日早讀文章由【圖書】PWA實戰:面向下一代的Progressive Web APP 作者@鄭豐彧翻譯授權分享。

@鄭豐彧,RxJS 中文社區創建者,《PWA實戰》《Angular權威教程》譯者。

正文從這開始~

內容概要: 你是否知道 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 的威力遠不止於此。我們首先來介紹到底什麼才是狀態管理。

狀態管理

如果你不確定這個“狀態”到底表示什麼,那我們用個更通俗的術語“”來進行替代。狀態是隨時間流逝而產生變化的數據。狀態決定了展示給用戶的界面。

到底什麼才是狀態管理?通常來說,在一個應用中管理的數據分為三種:

  1. 獲取並存儲數據

  2. 將數據分配給 UI 元素

  3. 改變數據

比如我們要做一個 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 帶來了強大的功能(這正是約束的力量!):

  1. 所有數據(應用的狀態)都必須能夠以文本的形式進行描述。你需要達到這種程度,用筆在紙上將所有的數據寫出來。(譯者注: 這裡的數據應該指數據結構和數據類型兩方面)

  2. 每個動作(改變數據的操作)都必須能夠以文本的形式進行描述。你必須在改變數據之前將其寫出來。沒有動作就無法改變數據。在 Redux 的術語中這稱之為 “派發 (dispatching) 動作”。

  3. 改變數據的代碼必須能夠像數學公式一樣運行。給定相同的輸入,必須返回同樣的結果。無論計算多少次,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 的三大原則嗎?它們正是祕訣所在。

自動反饋 Bug

想象一下,用戶發現應用中存在問題並想進行反饋。她煞費苦心地回憶和描述她所做過的一切。然後開發人員嘗試去手動執行這些步驟以查看 bug 是否復現。用戶的反饋很可能是模糊不清的。開發人員很難找到 bug 出現的原因。

如果是這樣呢,當用戶點擊 “反饋問題” 按鈕時,系統會自動地將用戶本地的狀態發送給開發人員。開發人員隨即點擊 “重發 bug” 按鈕便可查看 bug 究竟是如何產生的。Bug 隨即被修復,大家都很開心!

Redux Bug Reporter 就是這樣玩的。它的工作原理呢?Redux 的限制條件讓一切變成可能。

自動反饋 Bug (Beebee 作圖)

Redux 的缺點

Redux 的三大原則其實是一把雙刃劍。它開啟強大功能的同時也不可避免地帶來一些副作用。

陡峭的學習曲線

Redux 的學習曲線相當陡峭,需要時間去理解、記憶和熟悉它的模式。如果你完全不會 Redux 和 React ,不推薦你兩者同時學習。

“樣板” 代碼

在大多數情況下,使用 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 自身也需要去權衡。它開啟強大功能的同時也帶來了一些不可避免的缺點。一個開發團隊的職責就是進行評估,看如何進行取捨並作出明智的選擇。

作為設計師,如果我們能夠理解 Redux 的優缺點,我們就能夠從設計的角度為此決策作出貢獻。舉個例子,或許我們設計出的 UI 能夠緩解潛在的性能影響?也許我們可以提倡使用撤消/重做功能來替代大量的確認對話框?或許我們可以提倡 optimistic UI ,因為它能夠以相對較低的代價來提升用戶體驗。

理解技術的好處與侷限性,並作出相應的設計。這正是我對斯蒂夫·喬布斯的名言 “設計關乎於工作原理。” 的解讀。

關於本文
譯者:@鄭豐彧
譯文:
https://zhuanlan.zhihu.com/p/39503680
作者:@linton-ye
原文:
https://www.smashingmagazine.com/2018/07/redux-designers-guide/

最後,為你推薦


【圖書】PWA實戰:面向下一代的Progressive Web APP

閱讀原文

TAGS: