iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

3
Modern Web

深入現代前端開發系列 第 32

[番外篇] 再談 Redux

前言

番外篇的文章主要會分享一些影響我蠻多的框架或概念,今天來談論 redux 這個我很喜歡的函式庫。

再談 Redux

redux 是我相當喜歡的一套狀態管理方式,它將整個狀態變化的架構分成三大部分:

  • Action: 描述一個行為(可能是按下按鈕、輸入文字等),這個行為可能會造成狀態變化,通常 action 會用純物件表示。
  • Reducer: 接收一個行為,並且與 state 計算後回傳下一個 state,是一個純函數。
  • Store: 存放所有狀態的地方,統一透過 store.getState() 拿狀態

從這些描述上來看似乎過於抽象了,我們再來細談一些吧。

管理狀態為什麼那麼難?

管理狀態一直是開發上的難題之一,尤其是在前端那麼多狀態共存的情況下,怎麼優雅地管理狀態就是一大問題,到底管理狀態難在哪裡?

今天會從前端狀態管理遇到的問題跟 redux 試圖解決的問題談狀態管理。

  • 狀態容易被意外地改變,導致不好預測
  • 狀態可能被多個元件共享,如果想要在不同元件間共享狀態就要層層傳遞 prop
  • 在元件內撰寫改變狀態的邏輯,很容易讓元件變得臃腫肥大
  • 如果要做非同步的行為,像是 call API、讀取 File 等等,再搭配改變狀態的邏輯,全部寫在元件當中會相當不容易管理。
  • 在狀態改變時,我們希望通知所有監聽此狀態的元件或邏輯

也因此在前端社群有不少狀態管理的方案持續出現,像是 flux、redux、mobX 等等,都是為了解決類似的問題。

Redux 中的 middleware

在 redux 的世界中,想要改變狀態只有一個途徑,就是 dispatch 一個 action,同時確保有 reducer 做對應的狀態變化,除此之外別無他法,就連 store 當中也是只能從 getState 中拿取資料,沒辦法手動改寫。

這樣子確保了只有 action 才能夠讓狀態產生變化,在元件當中也只要 dispatch action,讓整個邏輯與操作變得簡單。

但要怎麼處理副作用?一個網頁應用正常來說都會有 Network 請求、Websocket 等操作,於是 redux 當中就有 middleware 的概念,在發送 action 前,可以透過 middleware 處理一切副作用(logging、async 等)。

像是 redux-observable、redux-saga、redux-thunk 等,都是讓非同步或具有副作用的操作可以與 redux 搭配使用,讓整個狀態管理更加清晰的方式。

這樣一來,在元件裡頭就不用寫各種 API 的處理,而是統一搬移到 middleware 這一層,除了實作元件上可以減少負擔之外也很好分工,讓開發者專注於開發元件,在適當的時機呼叫 action,串接 API 的部分就由另外一位開發者負責寫 reducer 與 action 等等。

react-redux

redux 在設計上只是一個單純的 store 而已,並沒有和任何框架(如 React、Vue 等)有任何綁定,也因此 react-redux 就是為了整合 react 與 redux 而衍伸出來的。簡單來說就是幫你把 store 中的資料注入 react 元件當中。

有些人或許會問,為什麼還要大費周章再加上 react-redux,而不直接用 store.subscribe 就好,何必還要寫什麼 mapStateToPropsmapStateToDispatch 呢?

  1. 元件不需要依賴外部的 store 注入,也不需要額外處理 unsubscribe 的邏輯,可以專注在元件實作本身
  2. react-redux 中做了非常多優化來避免不必要的更新,如果直接用 store.subscribe 會有很多不必要的渲染。
  3. 在 mapStateToProps 當中,為了確保每個 component 都可以拿到最新的 props,react-redux 中做了很多處理。
  4. stale-props-and-zombie-children,詳細可以參考官方文件或是這篇文章
  5. react-redux 血淋淋的歷史

缺點

對於 redux 來說,缺點也相當明顯,要定義各種 action 還有 reducer 是件相當惱人且枯燥的事,雖然有 redux-actions 之類的函式庫可以簡化,但還是一樣要做這些事情。
reducer 的執行會直接更新 store,也就是說每個 listener 都會被執行,雖然 react-redux 中做了許多優化,但檢查仍然需要時間,如果在短時間內大量送 action 在效能上或許就要不小的影響,而且這也代表著其他 action 的操作有可能會影響到整體更新的效能。

另外,當 reducer 一多,難免會增加不少 bundle size,這個時候要做 dynamic import 的話又需要額外做處理,而且也要確保在對應的元件載入的時候,對應的 reducer 也要一起載好,才不會拿不到 state。

小結

一個簡單的 connect() 實現可以參考 Dan 的 gist

理解背後的原理是什麼對於開發上是相當有幫助的,同樣的概念或許也可以用在其他地方上。最近越來越多「有了 xxx 就不需要 xxx」的聲音出現,如果不知道背後設計的原因與理念,也不了解歷史因素,那就是知識上的狂妄了。


上一篇
[番外篇] 什麼是好的程式碼、UI?
系列文
深入現代前端開發32

尚未有邦友留言

立即登入留言