EP09 | React 18 全家桶複習 - Redux Toolkit

使用 Redux Toolkit (RTK) 可以簡化傳統 Redux 的重複性操作。目前 Redux 也推薦使用 RTK。

RTK 創建 reducer 及生成對應 action

  1. 安裝 RTK 包 npm install @reduxjs/toolkit

  2. 在 reducer 下創建 slice 檔案,使用 createSlice 創建 reducer 及 action。

    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
    28
    29
    30
    31
    32
    // 1. 通過 creatSlice 來創建 reducer
    import { createSlice } from '@reduxjs/toolkit';
    export const todoSlice = createSlice({
    // name 用來生成 action 中的 type
    name: 'todos',
    // initialState 用來設定 state 的初始值
    initialState: {
    todoArr: [
    {
    userId: 1,
    id: 1,
    title: 'delectus aut autem',
    completed: false,
    }
    ]
    },
    // 指定 state 的各種操作,可以直接添加對 state 執行的方法
    reducers: {
    // 通過不同方法來指定對 state 的不同操作,
    deleteTodo(state, action) {
    // state 是一個代理對象,可以直接修改
    // state.name = "XXX"
    return state.todoArr.filter((item) => item.id !== action.payload);
    },
    },
    });

    // 3. createSlice 會自動生成 actionCreator,無須自己在創建,並導出讓組件可以來取得 action
    console.log(todoSlice.actions);
    // 透過解構賦值將 todoSlice.actions 拆開出來,每個都是函數
    // actionCreator 調用後的結構會是 {type: name/函數名, payload: 參數}
    export const { deleteTodo } = todoSlice.actions;
  3. 導入並透過 RTK 的 configureStore 來構建資料庫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 2. 透過 RTK 構建資料庫
import { configureStore } from '@reduxjs/toolkit';

// 3. 導入 reducer
import { todoSlice } from './reducers/todoSlice';

//4. 創建 store
export default configureStore({
// 使用到的 reducers
reducer: {
// 注意引入的是每個 slice 裡的 reducer 屬性
// 屬性名是之後要透過 state來訪問用的
todos: todoSlice.reducer,
},
});
  1. 在組件中使用 react-redux Hooks 取得或變更數據。
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
28
29
30
31
32
33
34
35
36
37
38
import { useSelector, useDispatch } from 'react-redux';
// 導入 action
import { deleteTodo } from './01.redux/reducers/todoSlice';

export default function Home() {
// 7. 透過 React-Redux hooks 中的 useSelector 來加載 state 數據
const todoArr = useSelector((state) => {
// state會是所有的資料
console.log(state);
return state.todos.todoArr;
});
// 8. 使用 React-Redux hooks 中的 useDispatch 來派發 action
const dispatch = useDispatch();
const deleteItem = (id) => {
// action 透過 createSlice 已經自動生成,可以直接取用(需先導入)
// id 為 action 的 payload
dispatch(deleteTodo(id));
};
return (
<div>
我是Home,放置所有 todo 資料
<hr />
<ul>
{todoArr.map((item) => (
<li key={item.id}>
{item.title}
<button
onClick={(e) => {
deleteItem(item.id);
}}>
刪除
</button>
</li>
))}
</ul>
</div>
);
}

RTK 異步數據請求

  1. 在 slice.js 中透過 createAsyncThunk 來處理異步數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
// A. 透過 createAsyncThunk 來創建異步的 actionCreator並實現異步數據取得
export const fetchTodoArr = createAsyncThunk({
// action.type
"todos/fetchTodoArr",
// payloadCreator(arg, thunkAPI),一個 Promise
async(args, thunkAPI) => {
// 可以透過 thunkAPI 來使用 store 的 getState、dispatch
// thunkAPI 還提供了 rejectWithValue 方法來處理錯誤訊息、fulfiiiWithValue 來處理成功訊息(但通常都是直接回傳資料,無須使用)
const res = await fetch(`https://jsonplaceholder.typicode.com/todo`);
if (res.status === 404) {
// 要記得 return
return thunkAPI.rejectWithValue("獲取數據失敗");
}
const json = await res.json();
// 給 reducer 的 action.payload
return json;
}
})
  1. 在 createSlice 中會有一個屬性 extraReducers 用來處理異步 action 傳回的資料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export todoSlice = createSlice({
//...
reducers: {
//會自動生成(同步) actionCreator 的 reducer 位置
},
//B. 將異步 action (由 createAsyncThunk創建)放置位置
extraReducers: {
// C. 異步數據成功取得後,要執行的 reducer 內容
[fetchTodoArr.fulfilled]: (state, action) => {
// 如果是重新賦值給state的話要記得 return return 的東西才是實際存在外面 store 的
state.todoArr = action.payload;
},
// C. 異步數據取得失敗後,要執行的 reducer 內容,action.payload 即是 rejectWithValue(內容) 回傳的內容
[fetchTodoArr.rejected]: (state, action) => {
state.status = 404;
state.errorText = action.payload;
}
}
})
  1. 在組件中依然是透過 react-redux Hooks (useSelector、useDispatch)來獲取資料。
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
// 導入 action
import { deleteTodo, fetchTodoArr } from './01.redux/reducers/todoSlice';

export default function Home() {
const todoArr = useSelector((state) => state.todos.todoArr);
const todoStatus = useSelector((state) => state.todos.status);
const todoErrText = useSelector((state) => state.todos.errorText);

const dispatch = useDispatch();

//D. 透過異步取得數據
useEffect(() => {
if (todoArr.length === 0) {
dispatch(fetchTodoArr());
}
}, []);
return (
<div>
我是Home,放置所有 todo 資料
<hr />
<ul>
{todoStatus !== 200 && todoArr.length === 0 ? (
<p>{todoErrText}</p>
) : (
todoArr.map((item) => (
<li key={item.id}>
{item.title}
<button
onClick={(e) => {
deleteItem(item.id);
}}>
刪除
</button>
</li>
))
)}
</ul>
</div>
);
}