---
# System prepended metadata

title: "\U0001F3C5Day40 - React 套件應用 (6) - Redux"
tags: [React 直播班 - 2025 冬季班]

---

# 🏅Day40 - React 套件應用 (6) - Redux

Redux 是一個開源的 JavaScript 狀態管理庫，通常與 React 或其他 JavaScript 框架一起使用，來管理應用程式的狀態。它最初是由 Dan Abramov 和 Andrew Clark 於 2015 年開發的，並迅速成為處理應用程式狀態管理的一個熱門工具。Redux 的設計理念是將應用程式的所有狀態儲存在一個單一的、不可變的數據結構中，稱為 store。

另外要注意，如果要在 React 中使用 Redux，必須搭配 react-redux 這個套件。React-Redux 是專為 React 開發的綁定庫，負責在 React 應用中更方便連接 Redux 的 store，並處理訂閱和狀態更新。

## Redux 的核心概念

### Store (儲存):
Redux 將應用程式的所有狀態儲存在一個全局的 store 中。這個 store 是一個單一的 JavaScript 對象，包含了應用程式的所有狀態。每次更新狀態時，都會創建一個新的 store，而不是直接修改原有的 store。Redux store 通常會存儲應用程式的所有數據（例如用戶資訊、UI 狀態等）。

### Action (動作):
Action 是一個普通的 JavaScript 對象，表示一個操作，通常包含類型 (type) 和可選的負載 (payload)。
每當我們希望更新 store 中的狀態時，就會派發 (dispatch) 一個 action。Action 必須有一個 type 屬性來描述操作的類型（例如 'INCREMENT'、'DECREMENT' 等）。
```js
const incrementAction = {
  type: 'INCREMENT',
  payload: 1
};
```

### Reducer (處理器):
Reducer 是一個純函數，它決定了如何根據收到的 action 更新 store 中的狀態。每個 reducer 都會接收兩個參數：當前的 state 和 action。根據 action 的類型，reducer 會返回一個新的 state。
在 Redux 中，狀態的更新是不可變的，因此每次更新都會返回一個新的對象。
```js
function counterReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + action.payload };
    case 'DECREMENT':
      return { ...state, count: state.count - action.payload };
    default:
      return state;
  }
}
```

### Dispatch (派發):
dispatch 是用來發送 action 到 store 的方法。每當我們想要改變 store 中的狀態時，必須調用 dispatch 來發送 action。
當 action 被發送後，reducer 會根據 action 類型來更新 store。
```js
store.dispatch(incrementAction);
```

### State (狀態):
Redux 的核心理念之一是應用程式的狀態應該是可預測的。所有狀態保存在 Redux 的 store 中，並且可以通過 getState 方法讀取當前狀態。

```js
const currentState = store.getState();
```

## Redux 工作流程
1. 應用程式啟動時：Redux store 初始化，並包含預設狀態。
2. 使用者操作：當使用者觸發某個事件（例如點擊按鈕），就會發送一個 action，並通過 dispatch 傳遞這個 action。
3. Reducer 處理 Action：根據 action 的 type，reducer 會更新 state 並返回一個新的 state。
4. 更新 UI：React 等前端框架會根據最新的狀態更新 UI。通常會使用 connect 或 useSelector 來訂閱 Redux store 的變更，並將狀態傳遞到組件中。

## Redux Toolkit

redux Toolkit 是官方推薦的 Redux 開發工具，旨在簡化 Redux 的使用方式，減少樣板代碼，提高開發效率。相比於傳統的 Redux，Redux Toolkit 提供了一些功能：

1. 簡化配置：使用 configureStore 來輕鬆設置 store，內建了 Redux DevTools 支援。
2. 免手動撰寫樣板代碼：createSlice 可以同時生成 reducer 和 actions。
3. 內建異步工具：支持 createAsyncThunk 簡化異步邏輯的處理。
4. 內建中介軟體：內建了 redux-thunk，可以直接處理異步操作。

在 Redux Toolkit 中，createSlice 是最常用的 API。

```js
// src/store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    incrementByAmount: (state, action) => {
      state.count += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;
```
用 configureStore 來建立 store，並加入 reducer。
```js
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export default store;
```
將 Redux store 注入到 React 應用中。

```js
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);
```
使用 useSelector 讀取狀態，useDispatch 發送 action。
```js
// src/App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './store/counterSlice';

const App = () => {
  const count = useSelector((state) => state.counter.count);
  const dispatch = useDispatch();

  return (
    <div style={{ textAlign: 'center' }}>
      <h1>Redux Toolkit 範例</h1>
      <p>計數器: {count}</p>
      <button onClick={() => dispatch(increment())}>增加</button>
      <button onClick={() => dispatch(decrement())}>減少</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>增加 5</button>
    </div>
  );
};

export default App;
```
### 異步操作範例
假如我們需要獲取一個 API 數據，createAsyncThunk 提供了簡化的方式：

```js
// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 定義異步邏輯
export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
  return response.json();
});

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

export default userSlice.reducer;
```


## 題目
請複製 （右下角 fork）這個[範例](https://codepen.io/yen-kg/pen/yLmWeBb?editors=1011)，利用 Redux 的方式替按鈕加上 +1 跟 -1 的功能。


<!-- 解答： 
```
const { createStore } = Redux;

const initialState = {
  count: 0
};

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

const App = () => {
  const [count, setCount] =    React.useState(store.getState().count);

  React.useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setCount(store.getState().count);
    });

    return () => unsubscribe();
  }, []);

  const increment = () => store.dispatch({ type: 'INCREMENT' });
  const decrement = () => store.dispatch({ type: 'DECREMENT' });

  return (
    <div>
      <h1>React + Redux 範例</h1>
      <p>計數器: {count}</p>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>減少</button>
    </div>
  );
};


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
```
-->
| Name | Codepen | 
| -------- | -------- |
| Jin | [codepen](https://codepen.io/Jin-L/pen/PwzBMZV)|
| Tetsu |[Codepen](https://codepen.io/ttgchang/pen/ByzPXdX)|
| Eric | [Codepen](https://codepen.io/wc-su/pen/dPXqbGE) |
| Nooooora | [CodePen](https://codepen.io/Nora-Ch/pen/LEZJPdP?editors=1011)|
| Chia |[Codepen](https://codepen.io/Chia-the-encoder/pen/dPXjxqQ)|
| RUDY |[Codepen](https://codepen.io/Rudy-crw/pen/OPXoJXQ?editors=0011)|
| 老屠 |[Codepen](https://codepen.io/fsgfxvlb-the-selector/pen/YPWjmrR?editors=0011)|
| Jenna |[Codepen](https://codepen.io/abiscc14/pen/MYeqppe)|
| 地瓜 |[Codepen](https://codepen.io/ChippyYU/pen/emzjqGW?editors=1010)|
| 平平 |[Codepen](https://codepen.io/ypinpin/pen/gbMdmdN)|
| 7lun |[Codepen](https://codepen.io/mfyvqhsn-the-bold/pen/Wbxgjpj?editors=1011)|
| Eileen |[Codepen](https://codepen.io/Eileen-io/pen/bNexMxN)|
| 星雪糖 |[Codepen](https://codepen.io/vfpofbgm-the-scripter/pen/zxBJLKg?editors=1011)|
| Percy |[Codepen](https://codepen.io/Percy-Ku/pen/vEKzPWm?editors=1010)|
| 小懿 |[Codepen](https://codepen.io/YT-the-vuer/pen/QwEVZdp?editors=0011)|
| 7Red | [Codepen](https://codepen.io/cch2655/pen/dPXqrOM) |
