React-Redux 觀念解析 - 01

管理state的好工具

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

概述

Redux 常見名詞解釋

  • React-Redux: 一個庫,它提供了一個將 React 和 Redux 集成在一起的方法,使得 React 組件可以輕鬆地獲取和操作 Redux store 中的狀態。
  • 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,可以在應用程式的任何部分存取狀態。這種方式比在 React 組件之間進行狀態傳遞要方便得多,因為不需要將狀態傳遞給每個組件。
  3. 容易實現時間旅行和調試:由於 Redux 中的所有狀態變更都是通過 action 進行的,因此可以輕鬆地實現時間旅行和調試。時間旅行是指可以回溯應用程式的狀態,以查看應用程式在不同時間點的狀態。這對於調試和測試非常有用。
  4. 易於與其他庫集成:由於 Redux 是一個獨立的庫,因此可以輕鬆地與其他庫集成。例如,可以使用 React-Redux 將 Redux 集成到 React 中,或使用 Redux-Saga 實現異步操作。
  5. 減少不必要的組件重新渲染:使用 Redux 可以避免在應用程式中出現因為無法有效管理狀態而導致的組件重新渲染問題。這是因為當 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 物件。

參考資料


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


留言版