Redux基礎必知必會 reducer拆分 中介軟體 單向資料流

IslandPolaris發表於2022-04-11

什麼是 redux? 三大原則?

什麼是 redux
Redux 是一個基於 js 的全域性可預測狀態容器,主要用於現代前端框架中進行全域性狀態管理,能夠在不同元件之間進行狀態共享
Redux 常與 React 配合使用,但它並非只能用於 React,由於 Vue 官方推出了自己的狀態管理庫 Vuex,因此 Redux 很少在 Vue 中使用
Redux 的實現借鑑了 Flux, 如單項資料流。但又有別於 Flux,如全域性單例 store

redux 三大原則

  1. 單一資料來源
    store 必須是唯一的,全域性的 state 儲存在單一 store 中

  2. state 是隻讀的
    state 只能通過派發 action 來改變

  3. reducer 必須是純函式
    reducer 只做一件事情,通過累積的 preState 和當前的 action 計算得出新的 state
    相同的輸入必須得到相同的結果,因此 reducer 必須是一個純函式

reducer 中不應該出現副作用,比如發請求


為什麼要用 redux

React 的定義是"一個用於構建 UI 介面的 javascript 庫",React 關注的點在於如何將狀態轉換為 UI(UI = fn(state)),在自帶的狀態管理方案中:

  • state: 適用於管理自身狀態,也可聯合 this.props.chidren 實現 callback render
  • props: 適用於父子元件傳值,但父子元件巢狀過深時這種方式過於繁瑣
  • context: 適用於父子元件巢狀過深和兄弟元件共享狀態的場景。context 一般是作為區域性的狀態管理方案而不是全域性的,因為 context 一旦更新,Provider 下包裹的所有子元件都會重新渲染,造成效能問題

在一個複雜的應用中,資料的流向存在跨層、反向的資料流, 在互動上也存在父子、兄弟、跨元件通訊, 不利於維護
而 Redux 只需要在最外層傳入 store, 內層元件即可通過 props 與 store 中的狀態互動
在 Redux 中,資料的流動是單向的,store 是全域性單一的,reducer 是純函式,同樣的輸入得到的輸出一定相同,因此狀態是可預測的

什麼時候用 redux

  1. 某個元件的狀態,需要讓其他元件可以隨時拿到
  2. 一個元件需要改變另外一個元件的狀態
  3. 如果你不確定要不要用 redux,那就一定不需要用 redux

redux 基本 api

createStore

createStore 是 redux 的重要組成部分, 大部分 api 都基於它生成
以下是 createStore 的形參列表

  1. reducer: 初始化、更新 state
  2. preloadedState: 預設初始化 state,但一般不用它而是 reducer 處理 init action 來初始化一個 state
  3. enhancer: 一般用於 applyMiddleWare 增加中介軟體,作用是對 createStore 進行增強,覆蓋原來的 dispatch 來實現一些功能上的擴充,如非同步 action、日誌列印、異常監聽

getState

獲取 state 的唯一方式,返回當前最新的 state
createStore 內部維持了一個變數 currentState,這個變數是私有的,對外部隱藏,只通過唯一介面 getState 對外暴露
react-redux connect 方法底層也是通過這個 api 拿到 state


dispatch

改變 state 的唯一方式,通過 dispatch 將使用者的行為以 action 的形式通知給 redux,通知 redux 把計算最新的狀態並反饋給使用者
如果你想在 dispatch 的前後做一些擴充功能,比如非同步 action,異常收集,日誌列印,建議使用中介軟體來做一些增強


subscribe

訂閱 store 的狀態變化,當且僅當 dispatch 觸發狀態更新之後執行入參 callback 回撥函式,並返回一個取消訂閱時執行的回撥函式。
通常我們使用這個 api 來實現 redux 和其他 js 庫通訊
react-redux 底層在 Provider 中通過 store.subscribe 發起訂閱,在 state 改變時,會檢查子元件是否通過 connect 消費了 store 以及子元件的 props 有變化,如果這兩個條件都滿足,就使用最新的狀態更新子元件,從而達到精確的最小粒度的 render,相對於 context 一旦資料更新就渲染所有 Provider 包裹的子元件而言,這種處理方式在效能上顯然是更優的


redux combineReducers 拆分reducer

隨著業務複雜度的增加,把所有狀態都放在一個 reducer 中進行處理只會讓程式碼變得更加難以維護
此時可以利用 combineReducers 拆分多個不同的 reducer,對不同業務模組的狀態進行分別管理

combineReducers 接受一個 reducers obj 作為入參,你可以給不同 reducer 指定 key,這樣呼叫 getState 時拿到的也是對應 key 的 state
如果多個元件要共享 state,使用相同的 reducer 即可

import {combineReducers, createStore} from 'redux'
import countReducer from './reducer'
import countReducer2 from './count2reducer'

const store = createStore(combineReducers(myCountState:countReducer,myCountState2:countReducer2}))
// store.getState() {myCountState:{...},myCountState2:{...}}

export default store

redux MiddleWare 中介軟體

如果要記錄狀態的歷史變化,你會怎麼做?
我們可能很容易想到只要在 dispatch 前後分別 log 一下就好了
但是一個複雜的大型專案,呼叫 dispatch 的地方不計其數,上述實現過於臃腫

所以我們應當使用中介軟體

中介軟體其實就是對 dispatch 的增強,中介軟體在 action 到達 store 的前後提供了邏輯插入點,我們可以在上面實現一些非同步 action、日誌輸出、異常檢測等功能

使用中介軟體
通過 applyMiddleWare 傳入中介軟體

const store = createStore(reducer, applyMiddleware(thunk))

redux 資料流向

  1. 初始化 store,此時 redux 會 dispatch 一個 type 為 init,payload 為 undefined 的 action 並從 reducer 拿到最初的 state
  2. 元件引入 store 並通過 getState 消費 store 狀態,通過 subscribe 訂閱 store 狀態變化
  3. 使用者動作產生 action,dispatch 派發 action 到 store 並通過 reducer 更新狀態。dispatch 的前後如果有中介軟體會在此時執行中介軟體相關邏輯
  4. 狀態更新發布之後通知訂閱者,訂閱者執行註冊訂閱的回撥函式,在回撥函式中可以呼叫 render 將最新的狀態展現給使用者,並等待使用者的下一次動作

相關文章