【文件筆記】Redux 官方文件


Posted by chihyu on 2021-01-25

參考來源:Redux 官方文件

Redux 簡介

甚麼是 Redux

是一個可以透過 actions 來管理和更新 state 的東西

  • 當在不同環境(client、server、native)執行時,也能保持一致
  • 容易測試
  • 提供良好的開發體驗
    ### 為甚麼需要 Redux
  • 管理 global 的 state
  • 可以清楚知道 state 更新的原因,就可以知道後續處理的邏輯

甚麼時候使用 Redux

  • 有很多地方需要共同用到很多 state 的時候
  • state 的更新很頻繁
  • 更新 state 的邏輯很複雜
  • codebase 很大、多人工作時

Redux Libraries、Tools

  • React-Redux:讓 React 的 component 可以跟 Redux 的 store 結合,透過發送 action 來更新 store
  • React Toolkit:用來寫 Redux 的邏輯
  • Redux DevTools Extension:追蹤 Redux store 裡 state 的歷史紀錄,可以讓 degug 更有效率
    (ex. time-travel-debugging)

Redux 術語、概念

state 的管理

單向 data flow 在規模大的時候,因為會有好幾個 component 共用同一個 state 的狀況,這樣可能會壞掉

解決方法:把 global state 集中到一個地方,利用特定模式把 state 更新

Immutability (不變性)

object、array 都是 mutable,要 immutable 更新值的話,必須先複製(解構) object、array,再去更新複製的值

Redux 更新值 immutably

術語

Actions:是一個 object,想像成 event,描述在 app 裡面發生的事情

type: domain/eventName
payload: information

const AddTodoAction = {
  type: 'todos/todoAdded', // 類別 / 事件名稱
  payload: 'Buy milk' // 發生的事
}

Action Creators:是一個 function,用來新建和回傳 actions 的 object

const addTodo = text => {
  return {
    type: 'todos/todoAdded', 
    payload: text 
  }
}

Reducers:是一個 function,用來接收 current state 跟 action 的 object,並回傳更新的 state

Reducers 依循的規則:

  • 根據 state 跟 action 來計算 state 的值
  • 複製 state 來更新 state 的值,以 immutable 方式更新 state
  • 不能執行非同步的邏輯、計算 random values、造成 side effect

Reducer 執行的步驟:

  1. 查看 action
  2. 如果 action 符合條件就複製 state
  3. 更新這個複製的 state 的值
    const initialState = { value: 0 }
    function conterReducer(state = initialState, action) {
    // 查看 action
    if (action.type === 'counter/increment') {
     // 符合條件就複製 state
     return {
       ...state
       // 更新 state 的值
       value: state.value + 1
     }
    }
    return state
    }
    

Store:是一個 object,用來存放 Redux 的 state

.getState 取得 state 的值

Dispatch:store 的一個 method,用 store.dispatch() 來更新 state

在 dispatch 裡面傳入 action creator,reducer 會作為 event listener 根據 action 來更新 state

Selector:是一個 function 用來選取 store 裡 state value 的特定資訊

Redux 的 data flow

初始設定

  • root reducer function 建立一個 redux store
  • store 呼叫一次 root reducer,儲存回傳值當作初始的 state
  • 當 UI 第一次 render 時,UI component 根據 Redux store 的 current state 來決定要 render 甚麼

更新

  • app 有甚麼變動的時候(ex. 使用者點擊按鈕)
  • app code 發送(dispatch) action 到 Redux Store
  • store 執行 reducer fucntion (之前的 state + 現在 action),產生的 value 會成為新的 state
  • store 會通知所有跟這個 store 有關的 UI,讓他們知道 store 有更新
  • 每個 UI 元件會檢查他們的所需的 state 的 data 是否改變
  • 如果元件發現他們的 data 有變,就會 re-render 成新的 data

Redux App 結構

安裝 counter expample

npx create-react-app redux-essentials-example --template redux

App 的 內容

創建 redux store

  • 透過 Toolkit 的 configureStore 建立 store
  • 在 configureStore 寫入 reducer 的 key 跟 reducer function
  • 透過 key 來更新 state

store 的範例:

import { configureStore } from '@reduxjs/toolkit'
import usersReducer from '../features/users/usersSlice'
import postsReducer from '../features/posts/postsSlice'
import commentsReducer from '../features/comments/commentsSlice'
export default configureStore({
  reducer: {
    users: usersReducer, // key: reducer
    posts: postsReducer,
    comments: commentsReducer
  }
})
  • state.users、state.posts、state.comments 是個別的 slice
  • 每個 key 對應的 reducer(slice reducer function) 用來更新對應的 slice

創建 Slice reducer functions、Actions

  • Toolkit 的 createSlice 處理 action type strings, action creator functions, and action objects
  • action type 的第一個字對應到 createSlice 的 name,第二個字則對應到 reducer function 的 key

Reducer 的規則

規則:

  • 根據 state、action 計算新的 state value
  • 不能直接更新現有的 state,要先複製 state,然後更新這個複製的 state
  • 不能做任何非同步的事情或其他 side effect

理由:

  • 比較容易知道程式碼怎麼跑的、知道如何測試

Reducer、immutable 更新

不可 mutate state 的原因:

  • 會產生 Bug,因為 UI 無法正確更新,顯示最新的 values
  • 很難去了解 state 是因為甚麼或是如何更新的
  • 很難寫測試
  • 無法正確發揮 time-travel debugging 的功能
  • 違反 Redux 的可預測性和使用方式

如何更新 state:copy original -> mutate the copy

用 thunk 編寫非同步邏輯

thunk 是一個特殊的 rudux function,可以包含非同步的邏輯

  • inside thunk fucntion: 取得 dispatch、getState 作為參數
  • outside creator function:建立並回傳 thunk function

React counter component

  • useSelector:是一個 hook,可以取得 state 裡面所需的值
  • 每一次 dispatch 或是 store 更新的時候,useSelector 就會重新跑一次 selector fucntion,如果 selector 回傳的值不同,useSelector 就會把 component rerender 成新的值
  • useDispatch:是一個 hook,幫我們進入 store,取得實際的 dispatch method

Component State、Form

  • 會在整個 app 用到的 Global state 才需要放到 Redux store,如果只會在一個地方用的 state,就只需要放到 component state
  • Global state --> Redux Store / Local state --> React Component

可以放到 Redux 的規則:

  • 是否有其他部分會在意這個 data ?
  • 是否會有這個 data 衍伸的 data ?
  • 相同的 data 是否被用來驅動多個 component?
  • 把 state 回復到一個給定的時間對你有價值嗎?ex.time travel debugging
  • 是否需要 cache data?
  • 是否想要在 hot-reloading UI component 時保持 data 一致?

提供 Store

<Provider></Provider>:把 store 傳下去,讓 useSelector、useDispatch 可以跟 provider 的 store 溝通

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

基礎 Redux data flow

Redux 的 state 由 reducer function 更新:

  • Reducer 計算新的 state,複製現有 state 的值,並把複製 state 的值改成新的值
  • createSlice function 會產生 slice reducer function,用來寫變更的 code,並以immutable 安全的更新
  • slice reducer functions 會被加到 configureStore 的 reducer field,並定義 Store 裡 data 和 state field 的名字

Component 透過 useSelector hook 去讀取 store 裡面的 data

  • Selector function 接收整個 state object,並回傳 value
  • 當 Redux store 更新時,Selectors 會重新跑,如果回傳的data 有變,component 就會 re-render

Component 透過 useDispatch hook 去 dispatch action 並更新 store

  • createSlice 會幫我們把每個添加到 slice 的 reducer 產生 action creator function
  • 在 component 呼叫 dispatch(someActionCreator()),去發送 action
  • reducer 執行並檢查 action 是否相關,如果有就會回傳新的值
  • 臨時的 data(form 的 input value) 會被存在 react component state,當完成 form 時,就會發送 action 更新 store

使用 Redux Data

任何 React Component 可以使用 Redux Store 的 data

  • 任何 component 可以讀取任何在 Redux store 裡的 data
  • 多個 component 可以同時讀取同個 data
  • component 應該取所需的 data 最小數量
  • component 可以結合 props、state、store 的值來決定要 render 甚麼,可以從 store 中讀取多個 data,並根據需要調整 data 以顯示
  • 任何 component 可以 dispatch action 來更新 state

Redux action creator 可以準備內容正確的 action object

  • createSlice、createAction 可以接受回傳 action payload 的 prepare callback
  • 獨特 ID 和其他隨機值應該要放到 action,而不是在 reducer 裡計算

Reducer 包含實際 state 的更新邏輯

  • Reducer 可以包含用來計算下個 state 的任何邏輯
  • Action object 應該包含剛剛好的訊息來描述發生的事

Async Logic and Data Fetching

用重複使用的 function 來從 state 讀取值

  • Selector 是一個 function,用 state 作為參數,並回傳一些 data

Redux 使用 middleware plugin 來實現 async 邏輯

  • 標準 async middleware 稱為 redux-thunk,已經包含在 Redux Toolkit 裡面
  • thunk function 接收 dispatch、getState 為參數,並用在 async 的邏輯

你可以發送額外的 action 來追蹤 API 的 loading status

  • 在呼叫 api 之前發送 pending action,success 包含 data、failure 包含 error
  • loading state 應該存成 enum

Redux Toolkit 有一個 createAsyncThunk 的 API,可以發送這些 action:

  • createAsyncThunk 會接受一個會回傳 Promise 的 "payload creator" callback,並自動產生 pending/fulfilled/rejected 的 action types
  • fetchPosts 會根據回傳的 promise 發送 actions
  • 利用 extraReducers 去監聽 createSlice 裡的 action types,並根據 action 來更新 reducer 的 state
  • Action creator 可以自動填入 extraReducers object 的 key,這樣 slice 就可以知道要監聽哪個 action

性能、規格化 data

Memoized selector functions 可以優化性能

  • Redux Toolkit 從 Reselect 重新 export createSelector function,產生 memoized selectors
  • 如果 input selector 回傳新的值,memoized selectors 就會重新計算結果
  • 記憶化可以跳過龐大的計算,也能確保回傳的結果來源相同

用 Redux 優化 React component 的 patterns

  • 避免在 useSelector 新增 object/array references,會造成不必要的 re-render
  • Memoized selector functions 可以傳進 useSelector 來優化渲染
  • useSelector 可以接受一個 alternate comparison function(ex. shallowEqual),代替 reference equality
  • component 可以放在 React.memo() 裡,以便只有在 props 改變時 re-render
  • 可以透過 parent component 讀取 children 的 ID,透過 ID 取得 children 的 item

推薦用 Normalized state 結構儲存 items

  • "Normalization" 指不複製 data,用 item ID 把 item 存在一個 lookup table(查找表)
  • Normalized state shape usually looks like {ids: [], entities: {}}

Redux Toolkit's createEntityAdapter API helps manage normalized data in a slice

  • item ID 可以用 sortComparer 分類排序
  • adapter object 包含:
    • adapter.getInitialState:可以接受額外的 state field(ex. loading state)
    • prebuilt reducer:setAll, addMany, upsertOne, and removeMany
    • adapter.getSelectors:會產生 selector 像 selectAll、selectById

#Web #Redux







Related Posts

JavaScript 核心 - 來講講提升(hoisting)

JavaScript 核心 - 來講講提升(hoisting)

8 月開始上班,切換行銷與寫程式的腦袋

8 月開始上班,切換行銷與寫程式的腦袋

Tailwind CSS 引用 (使用nuxt)

Tailwind CSS 引用 (使用nuxt)


Comments