# [番外篇] 再談 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 (opens new window),詳細可以參考官方文件或是這篇文章 (opens new window)
  5. react-redux (opens new window) 血淋淋的歷史

# 小結

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

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