Django + React 電商專案練習 [1] - react-redux 觀念筆記

管理state的好工具

Posted by Young on 2022-11-20
Estimated Reading Time 8 Minutes
Words 2.1k In Total

概述

Redux 常見名詞解釋

  • React-Redux:一個套件,讓 React 跟 Redux 可以一起使用,幫助 React 元件輕鬆讀取或更新 Redux 的狀態。
  • Action:一個物件,用來描述發生了什麼事情,包含改變應用程式狀態需要的資訊。
  • Reducer:一個純函式,負責接收 action 和目前的狀態,然後回傳更新後的新狀態。
  • Store:一個物件,負責保存應用程式的所有狀態,並提供方法來讀取狀態或監聽狀態的變化。
  • Dispatch:一個函式,負責把 action 傳給 reducer,進而觸發狀態的改變。
  • Middleware:一個可以延伸 dispatch 功能的工具,常用來處理非同步操作、記錄日誌或錯誤處理等。
  • Selector:一個函式,用來從狀態中挑選出需要的資料,讓 React 元件更方便拿來用。

為何需要 Redux ?

原本在 React 中,使用 createContext 可以定義一個 context,並在 provider 中指定要共享的 state 結構,這樣可以在元件之間傳遞 props。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { createContext, useState } from "react";

// 定義一個 context
export const MyContext = createContext();
function App() {
// 設定要共享的狀態
const [count, setCount] = useState(0);
return (
// 使用 provider 共享狀態
<MyContext.Provider value={{ count, setCount }}>
<div>
<h1>Count: {count}</h1>
<MyComponent />
</div>
</MyContext.Provider>
);
}

function MyComponent() {
// 從 context 中獲取共享的狀態
const { count, setCount } = useContext(MyContext);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increase Count</button>
</div>
);
}

然而,當需要管理大量的 context 時,專案中的階層變複雜,state 和 props 變的很多,資料在多層 component 之間的傳遞也越來越多,就會產生一堆純粹用來傳遞用的 props 和父 component。為了提高可維護性,需要使用更有效的方法。

React 專案導入 Redux 主要目的

  1. 集中管理狀態:Redux 把應用程式的狀態統一存放在 store 裡,方便管理。不需要東一塊、西一塊的處理狀態,讓整體更有條理,也更好維護。
  2. 簡單取得狀態:用 Redux 可以在應用的任何地方直接存取狀態,不需要一直用 props 一層層傳遞資料,省時又方便。
  3. 好調試、支援時間回溯:Redux 的狀態改變都是透過 action,這讓我們可以回溯應用程式的狀態,檢查不同時間點的狀態變化,對於調試和測試超級有幫助。
  4. 容易整合其他套件:Redux 是一個獨立的套件,可以輕鬆跟其他工具搭配使用,比如用 React-Redux 把 Redux 接到 React 裡,或用 Redux-Saga 處理非同步邏輯。
  5. 避免不必要的重渲染:Redux 可以有效管理狀態,當狀態改變時,只有用到這些狀態的元件會重新渲染。這能減少多餘的重繪,提升效能。

觀念

它們之間的關係是 action 觸發 reducer 更新 store 中的 state。

action_reducer_store_state

三個核心概念

  • action:action 描述 state 改變
  • reducer:reducer 根據 action 更新 state
  • store::store 存儲 state 並提供存取

三原則

  • 單一資料來源 (Single source of truth):整個 App 的資料狀態(state)都被存儲在一個稱為 “store” 的物件中,方便維護和操作。
  • 可變的狀態(State is read-only):要改變資料狀態的唯一方式就是透過 action,而不是直接修改狀態本身。這樣可以讓應用程式的狀態變更變得可追蹤、可預測,也更容易進行調試和測試。
  • 使用純函數來進行修改(Change are made with pure functions):狀態的變更 abstract 成一個稱為 “reducer” 的純函數,該函數接收一個舊的 state 和一個 action,並返回一個新的 state 。純函數可以避免副作用和 state 變化的不可預測性。

Redux 運作流程 (data flow)

redux_data_flow

元件

Action、Action Creators 和 Bound Action Creator

action 是描述狀態變更的純 JavaScript 物件(object)。它必須包含一個「type」屬性,用來描述要發生的事件(說明想要對資料做哪些改變),並且可以包含任意額外的資料 (payload)。

1
2
3
4
5
6
7
8
9
10
// action creater
const increaseCount = () => {
return {
// action
type: "INCREASE_COUNT",
payload: {
amount: 1,
},
};
};

這邊範例定義了一個名為「increaseCount」的 action,它將 type 設置為「INCREASE_COUNT」,並且在 payload 中傳遞了一個名為「amount」的數值。當這個 action 被 dispatch 到 store 中時,它將會觸發狀態的變更,進而影響應用程式的行為。

通常會在一個獨立的檔案中定義所有的 action,然後在需要的地方引用它們。使用 Redux 提供的 action 創建函數,我們可以輕鬆地創建一個新的 action 物件,並將其 dispatch 到 store 中。

1
2
// Bound Action Creator dispatch action
dispatch(fetchData(3));

Reducers 更新與改變 State

rreducer 是一個純函式(function),reducer 接收先前的 state 和一個 action,並回傳新的 state。reducer 是用來處理 action 的,它會判斷 action 的 type,然後根據不同的 type 做出不同的處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const initialState = {
count: 0,
};

const counterReducer = (state = initialState, action) => {
switch (action.type) {
case "INCREASE_COUNT":
return {
...state,
count: state.count + action.payload.amount,
};
case "DECREASE_COUNT":
return {
...state,
count: state.count - action.payload.amount,
};
default:
return state;
}
};

在這個範例中,定義了一個名為「counterReducer」的 reducer,它會對應到一個名為「count」的狀態屬性。當 reducer 接收到一個 action 時,它會檢查 action 的 type,然後決定如何處理狀態的更新。在這個例子中,我們使用 switch/case 來處理兩種不同的 action:

  • 當 action 的 type 為「INCREASE_COUNT」時,我們會將 count 屬性增加 action.payload.amount 的值。
  • 當 action 的 type 為「DECREASE_COUNT」時,我們會將 count 屬性減少 action.payload.amount 的值。
  • 2如果 action 的 type 不符合上述任何一個條件,則直接回傳原始的 state。

reducer 必須是一個純函式,也就是說,它不能有任何副作用,必須總是返回一個新的 state。這種限制可以確保 Redux 的行為可預測,並且簡化了程式的測試和調試。

Store 儲存所有 State

state 統一存在 “store” 物件中,方便維護和操作,在 Redux 中只會有一個 store,但可以有多可 reducer

state 是一個包含所有應用程式狀態的物件。為了避免直接修改 state,Redux 採用了 immutable 的概念,也就是每次更新狀態都必須回傳一個全新的 state 物件,而不是修改原有的 state 物件。

所以才常看到用 spread operator 來複製一個新的 state 物件的寫法:

1
2
3
4
5
6
export const userDetailReducer = (state = {}, action) => {
switch (action.type) {
case USER_DETAIL_REQUEST: // 用 spread operator 保留原有的 state,遵循 immutable 原則的原則,不應該直接改變原始狀態
return { ...state,loading: true};
..........
};

這樣的寫法有幾個好處:

在 Redux 中,我們可以使用 store.getState() 方法來獲取當前的 state 物件,但不能直接修改它。我們必須通過發送 action 來更改 state,並且這個更改必須在 reducer 中進行。由於 reducer 是純函式,因此我們可以安全地修改 state,並在每個 action 被處理後返回一個新的 state 物件。

  • 它可以使 Redux 的行為更可預測,因為我們不會意外地修改 state 物件。
  • 它使我們可以更輕鬆地進行調試和測試,因為我們可以比較新舊兩個 state 物件,並檢查它們的差異。
  • 它可以使我們更容易地實現時間旅行(time travel)功能,即將應用程式回到過去的狀態。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { configureStore } from "@reduxjs/toolkit";

export const initialState ={
cart : { cartItems:cartItemsFromStorage}
};
// 處理異步 action creator
const middleware = [thunk];

const store = configureStore({
reducer: { // 儲存多個state的地方,到時會再各個component用useSelector指定要使用哪一個
ProductList : ProductListReducer,
ProductDetail : ProductDetailReducer,
cart:cartReducer,
},
preloadedState: initialState,
middleware: middleware,

export default store
});

簡單來說,在 Redux 中,state 是一個包含應用程式狀態的物件,它可以被修改,但必須在 reducer 中進行,並且每次修改都必須返回一個新的 state 物件。

參考資料


若您覺得這篇文章對您有幫助,歡迎分享出去讓更多人看到⊂◉‿◉つ~


留言版