# 第七週
本頁面連結: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 的更新方法。

## 從零建構 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)

- 團隊任務:
- https://rpg.hexschool.com/#/training/12062543649513962870/board/content/12062543649513962876_12062543649513962913