---
# System prepended metadata

title: 第七週
tags: [React 直播班 - 2024 冬季班]

---

# 第七週

本頁面連結：https://hackmd.io/UL3k5CHxRl2C87HPnI2Tcw

## 完課獎勵說明

完整獎勵說明：https://hackmd.io/ry4GF6KWROKbx9cLf51bkA

RPG 獎勵說明連結：https://rpg.hexschool.com/#/training/12062543649513962870/board/content/12062543649513962883_12062543649513962886

- 2/16 前 50%：超過 100 人挑戰成功，全班直播加碼授權一年
    - 前完成課程報到 -> 第四週主線
- 2/23 前 60%：框架菁英班直播 → 主題：Next.js 起步走
    - 完成課程報到 -> 第五週主線 或 第六週主線 + 心得牆
- 3/9 前 80%：框架菁英班錄影(目前以 Vue 為主，React 可參考主題包含：從零建構 API、Cloud flare、前端工程師如何寫出好履歷)
    - 完成課程報到 -> 第七週主線 或 第六週主線 + 心得牆
- 100%：數位完賽獎狀

目前第四週已經有超過百人完成，但請記得完成 “報到任務” 喔～

心得牆任務：https://rpg.hexschool.com/#/training/12062543649513962870/board/content/12062543649513962881_12062543649513962910
心得牆素材：https://hackmd.io/OcMERTDpT86G-5PS8NDVjg

## 提醒：作業繳交說明

- 所有繳交期限 3/23
- **最終延長繳交條件**：3/23 前完整繳交（沒有被直接退件），可延長批改至 4/13
    - 直接退件的原因：
        - 助教下載後，`npm run dev` 無法運行
        - 作品不完整（作品內容有缺、有大量假字）


主要介紹：

- 為什麼要跨元件狀態
    - 跨元件計數器
    - Redux toolkit
- Redux 非同步處理方式
- Redux toolkit 專案管理方式

## 跨元件狀態

遇到的問題：

- 狀態都要由父層處理（透過 Props），專案一大會顯得混亂
    - 非同步處理時，也會在父層加入大量的程式碼
    - 如果資料操作複雜時，複層會更顯得混亂
- useContext 在大型專案管理會更加複雜

## 什麼是 Redux Toolkit

**名詞介紹：Store**

- **Slice：** 一個用儲存狀態的容器。
- **Store:  全域的狀態儲存**，用於保存所有相關的 Slice 的狀態。
- **Provider:** 用於將 Store 的數據傳遞給其他元件
- **State**: 狀態本身
- **Action:** 動作或操作，用於觸發 Reducer 更新 State
- **Reducer:** 處理 Actions 並更新 State 的函式。每個 Reducer 接收兩個參數：Action 和當前 State，然後返回新的 State

**元件方法**

- **useSelector:** 用於在元件中存取特定的 State
- **useDispatch:** 用於在元件中觸發 actions，進行State更新。透過 useDispatch 可以直接呼叫 Store 的更新方法。

![空白](https://hackmd.io/_uploads/S1yxoh2YJx.png)



## 從零建構 Redux

**一、安裝環境**

https://redux-toolkit.js.org/introduction/getting-started#an-existing-app

```jsx
npm install @reduxjs/toolkit
npm install react-redux
```

**二、建立第一個 Slice（store 中一定需要 slice）**

路徑： `src/slice/counterSlice.js` 

- name, initialState 是必要的屬性
- 匯出該 Slice 的 reducer（這是固定命名

```jsx
import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
  name: "counter",
  initialState: {
    count: 0
  },
});

export default counterSlice.reducer;
```

> 小型的資料模組，可以用來管理資料、操作資料的方法等
> 

**三、建立 Store（管理 Slice 使用）**

路徑： `src/store.js`

1. 匯入 configureStore 的方法，並用它來建立 store
2. 從 Slice 中匯入 reducer

```jsx
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./slice/counterSlice";

export const store = configureStore({
  reducer: { // 必要加入 reducer
    counter: counterReducer
  }
});
```

> 這裡的 reducer 算是一個集合管理器，將 slice 匯入後統一進行管理
> 

**四、在專案中加入 RTK**

檔案：`main.jsx`

- 需要匯入 store, Provider
- 將 Provider 包在 App 或是 RouterProvider 外層（看有沒有使用 ReactRouter

```jsx
import { store } from './store'
import { Provider } from 'react-redux'

createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <RouterProvider router={router} />
  </Provider>
)
```

> 將 store 整個加入至專案中，讓內層所有的元件都能存取 store 所管理的資料
> 

**五、將數據從 store 中取得**

- 匯入 useSelector 方法
- 定義資料，並從 store 中取得

```jsx
import { useSelector } from 'react-redux';

function CounterRender() {
  const count = useSelector((state) => {
    console.log(state); // 可以看到全部的 store 數據
    return state.counter.count
  });
  return (<div>RTK: {count}</div> );
}
```

> useSelector 就像是筷子，直接從 slice 存取你的資料
且當狀態更新時，useSelector 可以協助刷新元件
> 

> 到這個步驟，基礎環境已經建立完成
> 

**六、建立 Actions**

路徑： `src/slice/counterSlice.js` 

```jsx
import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
  // ...
  reducers: { // 新增一個 reducers 物件，並添加方法
    addCount: (state) => {
      state.count += 1;
    }
  }
});

export const { addCount } = counterSlice.actions; // 從 slice 中的 reducers 中匯出該方法

```

> 這個 reducers 跟外層有點不同，可以理解成更新當前 slice 的方法集
> 

**七、匯入 actions**

兩個方法皆可運作

1. 直接使用 `dispatch({type: {reducerName} / {actionName}})`

```jsx
import { useDispatch } from 'react-redux';
function CounterBtn() {
  const addCountByClick = () => {
    **dispatch({ type: 'counter/addCount' });**
  };

  return (
    <button onClick={() => addCountByClick()}>RTK:點我加一 {count}</button>
  );
}
```

2. 從 slice 帶入 action 方法，直接呼叫他 `dispatch({action()})`

```jsx
import { useDispatch } from 'react-redux';
import { addCount } from '../slice/counterSlice';

function CounterBtn() {
  const addCountByClick = () => {
    **dispatch(addCount());**
  };

  return (
    <button onClick={() => addCountByClick()}>RTK:點我加一 {count}</button>
  );
}
```

> 第二種方式比較不會拼錯
> 

**八、傳入參數**

1. 在 slice 中建構方法，並在 action 中取得參數

action.payload 中，可以找到傳遞的參數，在此可以先透過 console 檢視

另外新建立的方法，也一樣需要透過 export 進行匯出

```jsx
export const counterSlice = createSlice({
  // ...
  reducers: {
    addCountByAmount: (state, action) => {
      console.log('addCountByAmount', action);
      state.count += action.payload;
    }
  }
});

export const { addCount, addCountByAmount } = counterSlice.actions;
```

1. 元件中，透過以下兩種方式擇一觸發

```jsx
// 直接傳遞 payload
dispatch({ type: 'counter/addCountByAmount', payload: 5 });

// 透過呼叫 action 加入參數
dispatch(addCountByAmount(5));
```

> 很好，再來建構一次相減的方法
> 

流程複習：

1. 在 slice 中建構相對應 reducer → **建構 reducer**
2. 元件內匯入特定方法 → **匯入方法**
3. 呼叫、測試運作是否正確 → **呼叫、測試**

## useSelector 簡化方法

如果不想要每次 useSelector 都寫很長的 callback function，那麼可以…

1. 在 slice 檔案，就先寫好 callback function

```jsx
export const selectCount = (state) => state.counter.count;
```

2. 匯入使用（這個結果與前者相同

```jsx
const count = useSelector(selectCount);
```

3. 你也可以在此做複雜的處理（單一值為主）

```jsx
// slice.js
export const selectCount2 = (state) => state.counter.count * 2;

// component
const count2 = useSelector(selectCount2);
```

如果匯出資料，需要整合多個 State…

## Redux 與非同步

- reducers **本身僅能處理同步的狀態**
    - 如果在 reducers 的方法加入非同步事件，如 setTimeout 或 ajax，則會出現 `*Cannot perform 'get' on a proxy that has been revoked` 錯誤*


#### 解決方案一：createAsyncThunk
使用另一個方法 createAsyncThunk 處理完非同步後，在交回給 reducers 處理

1. 建構 createAsyncThunk 方法

參數包含： createAsyncThunk(自定義名稱, 非同步函式(值, 參數))

名稱通常使用 `sliceName/actionName` 的格式，且需要是唯一的

```jsx
export const addAsyncCountByCreateAsyncThunk = createAsyncThunk(
  'counter/addAsyncCountByCreateAsyncThunk',
  // params 中包含 dispatch 等方法
  async (amount, params) => { // (傳入的值, asyncThunk參數)
    console.log('addAsyncCount:', amount, params);
    setTimeout(() => {
      params.dispatch(addCountByAmount(amount));
    }, 1000);
  },
);
```

2. 呼叫該方法

```jsx
import { addAsyncCountByCreateAsyncThunk } from '../slice/counterSlice';

元件中...
addAsyncCountByCreateAsyncThunk(...)
```

優化：

1. 使用常數命名
2. 解構出 dispatch 做使用

```jsx
export const ADD_COUNT_BY_ASYNC = createAsyncThunk(
  'counter/ADD_COUNT_BY_ASYNC',
  async (amount, {dispatch}) => {
    setTimeout(() => {
      dispatch(addCountByAmount(amount));
    }, 1000);
  },
);
```

#### 解決方案二：在元件裡處理完非同步

就．．．，不要在 Redux 中處理非同步


## 透過 JSON PlaceHolder 練習 Redux

1. 從零建構一個新的 slice
2. 了解在目前流程中，會有哪些？
    1. 控制資料的行為 → Reducers
    2. API 行為 → createAsyncThunk




## 本週額外挑戰

- 作業連結：https://rpg.hexschool.com/#/training/12062543649513962870/board/content/12062543649513962871_12062543649513962892?tid=12062543649528575021
    - 助教作業解說（週日上午 10:00）
    ![image](https://hackmd.io/_uploads/BkXeKpsSke.png)
- 團隊任務：
    - https://rpg.hexschool.com/#/training/12062543649513962870/board/content/12062543649513962876_12062543649513962913