目前這個系列已挑選ChatGPT、前端&React相關章節出版成書!
感謝大家的閱讀與支持!
《用 ChatGPT 詠唱來點亮 React & 前端技能樹》
歷經重新整理、校正、編排、與修改後,讓章節內容之間的銜接更平順,除了補充上更多的解說之外,甚至有部分章節為重新撰寫的內容,讓理解變得更輕鬆!
目前已在天瓏書局、博客來、金石堂等通路都買得到了! 也有電子書的選擇!
歡迎各位點擊連結選購,謝謝大家~
在Day16的時候,介紹了useState這個管理、更改狀態的hook
透過 count [count, setCount] = useState<number>(0) 的語法
讓我們拿到 count這個狀態變數,以及setCount 這個更改狀態的function
在複雜的專案中,單用useState 可能還是會造成管理混亂setCount 的使用方式並沒有限制,在更改state的時候邏輯比較鬆散
可以隨意的 setCount(count+2)或是 setCount(count*2)
在大型且複雜專案的管理狀態上,還有另外一個工具可以更嚴謹的幫助我們在React中管理state
——React Redux
這個章節讓ChatGPT介紹什麼是 react redux
運作流程和方式又是什麼,GO GO

嗨!當然可以,讓我來為你介紹一下 React Redux。
首先,React Redux 是一個用於管理 React 應用程式狀態的庫。它結合了 React 和 Redux,這兩者可以讓你更有效地管理你的應用程式狀態。
接下來,讓我來講解一些 Redux 的基礎觀念:
- 單一資料來源 (Single Source of Truth):
Redux 的核心理念是將整個應用程式的狀態保存在一個稱為 "Store" 的物件中。這個 Store 是應用程式的單一資料來源,這表示所有的狀態都會以一個物件樹的形式存放在這個 Store 中。- 狀態是唯讀的 (State is Read-Only):
在 Redux 中,狀態是唯讀的,這表示你無法直接修改狀態。如果你想要改變應用程式的狀態,你必須派發一個 "Action"。- 使用純粹函數 (Changes are made with pure functions):
為了改變應用程式的狀態,你需要編寫一個稱為 "Reducer" 的純粹函數。Reducer 接收一個當前狀態和一個 Action,並回傳一個新的狀態。- 單向數據流 (Unidirectional Data Flow):
Redux 強調了單向數據流的概念,這表示資料的流動是單向的,從 Store 到 View,然後是從 View 到 Action。- 使用Action來描述事件 (Actions describe events):
Action 是一個用來描述發生了什麼事件的物件。它會携帶一些基本資訊,例如事件類型 (type) 和相關的資料。總的來說,Redux 提供了一個有效的方法來管理應用程式的狀態,特別是在大型或複雜的應用程式中。透過將狀態統一儲存在一個 Store 中,並使用 Actions 和 Reducers 來管理狀態的改變,我們可以保持應用程式的可預測性和可維護性。
希望這份簡介對你有所幫助!如果你有任何其他問題或需要更多解釋,請隨時告訴我。
講了一些觀念,提到了「Store」、「Actions」、「Reducers」
但可能還是太過抽象了,相當的咬文嚼字
讓ChatGPT用比較好懂的比喻來講述流程看看
當然!我用一個生活中的比喻來解釋 Redux 的運作流程:
想像你在玩一個角色扮演遊戲,你的角色身上有很多屬性,比如生命值、魔法值等等。這些屬性都存在於一個特別的袋子裡,稱為「屬性袋」。
- Store (屬性袋):
- 在 Redux 中,我們有一個特別的袋子叫做「Store」,裡面存放著所有的應用程式狀態,就像是你角色的所有屬性都在這個屬性袋裡。
- Action (命令):
- 當你想改變你角色的某個屬性,比如增加生命值,你會發出一個特定的指令,比如「加生命值」。
- Reducer (處理器):
- 這個指令會傳給一個處理器,就像是一個會根據你的指令執行特定動作的魔法師。這個魔法師會根據指令,把你的角色的生命值加上指定的數量。
- 新的狀態 (更新後的屬性袋):
- 處理器完成工作後,會給你一個更新後的屬性袋,裡面的屬性已經被修改過了。
這樣就完成了一個簡單的 Redux 流程:
- 你想要改變某個狀態,就像是想要增加生命值。
- 你發出一個特定的指令(Action),比如「加生命值」。
- 這個指令被傳給處理器(Reducer)。
- 處理器根據指令,修改狀態,然後給你一個更新後的狀態(屬性袋)。
這樣,Redux 就像是一個角色扮演遊戲中的魔法師,幫助你管理角色的屬性。希望這個比喻能讓你更容易理解 Redux 的運作流程!如果你有任何問題,隨時告訴我。
雖然用詞很奇怪,但勉勉強強,列舉各項名詞和比喻之後好一點…點…
還…還是看不懂就畫圖!
當然,我可以用 Mermaid 語法來表示 Redux 的運作流程。以下是一個簡單的 Mermaid 流程圖,說明 Redux 的基本流程:
graph TD A[Action] -->|觸發| B[Reducer] B --> C[Store] C -->|更新| D[View] D -->|互動| A在這個流程圖中,我們有以下幾個元素:
- Action (行動):代表應用程式中的某個事件,它可以觸發狀態的改變。
- Reducer (處理器):處理 Action,並根據 Action 的類型來修改應用程式的狀態。
- Store (儲存庫):存放整個應用程式的狀態,是所有狀態的「單一來源」。
- View (視圖):展示應用程式的介面,當狀態改變時,會更新並顯示新的資料。
流程如下:
- 一個 Action 被觸發,通常是由使用者的操作或其他事件引起的。
- 這個 Action 被送到 Reducer,Reducer 根據 Action 的類型來修改應用程式的狀態。
- 修改後的狀態被存放在 Store 中,它是整個應用程式的狀態管理中心。
- 當狀態改變時,View 會被更新以反映新的狀態。
- 這個流程可以一直循環,每當有新的 Action 被觸發,就會再次進行狀態的修改和更新。
這就是 Redux 的基本運作流程。希望這個 Mermaid 流程圖有助於你更清楚地理解 Redux 的運作方式。如果你需要更多詳細資訊或有其他問題,請隨時告訴我。
畫成流程圖的話就會是這樣
所以Redux用這樣的模式來更新「Store」,也就是原本的狀態(State)集結成庫
來看一下如何開始使用Redux以及範例程式碼

當然可以!首先,讓我為你介紹一下 React Redux。
關於 React Redux
React Redux 是一個結合了 React 和 Redux 的庫,用於更有效地管理 React 應用程式的狀態。它幫助你在 React 應用中更容易地使用 Redux。
React Redux 提供了兩個主要的元件來實現這個整合:
- Provider: Provider 是一個 React 元件,它使你的整個應用程式能夠訪問 Redux Store。你只需要在你的應用程式的最上層使用 Provider,就可以讓所有的子元件都能使用 Redux。
- connect 函式: connect 函式是一個用於連接 React 元件和 Redux Store 的函式。它會將 Store 中的狀態以及 Action 傳遞給相關的 React 元件。
現在,讓我們來製作一個簡單的 ToDo List 的範例,並使用 functional component 以及 hook 來實現它。
ToDo List 範例
步驟 1:建立 Redux Store
首先,你需要安裝 Redux 和 React Redux:
npm install redux react-redux然後,在你的應用程式中建立一個 Redux Store:
// src/store.js import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore(rootReducer); export default store;步驟 2:建立 Reducer
建立一個簡單的 Reducer 來處理 ToDo List 的狀態:
// src/reducers/todoReducer.js const initialState = { todos: [], }; const todoReducer = (state = initialState, action) => { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [...state.todos, action.payload], }; case 'REMOVE_TODO': return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload), }; default: return state; } }; export default todoReducer;步驟 3:建立 Action Creators
創建一些 Action Creators 來描述你要進行的操作:
// src/actions/todoActions.js export const addTodo = (todo) => { return { type: 'ADD_TODO', payload: todo, }; }; export const removeTodo = (id) => { return { type: 'REMOVE_TODO', payload: id, }; };步驟 4:建立 ToDoList 元件
使用 functional component 和 hook 來建立 ToDo List 元件:
// src/components/ToDoList.js import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { addTodo, removeTodo } from '../actions/todoActions'; const ToDoList = () => { const [input, setInput] = useState(''); const todos = useSelector(state => state.todos); const dispatch = useDispatch(); const handleAddTodo = () => { dispatch(addTodo({ id: Date.now(), text: input })); setInput(''); }; const handleRemoveTodo = (id) => { dispatch(removeTodo(id)); }; return ( <div> <h2>ToDo List</h2> <div> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={handleAddTodo}>Add</button> </div> <ul> {todos.map(todo => ( <li key={todo.id}> {todo.text} <button onClick={() => handleRemoveTodo(todo.id)}>Remove</button> </li> ))} </ul> </div> ); }; export default ToDoList;步驟 5:使用 Provider 連接應用程式
在你的應用程式的最上層,使用 Provider 來連接 Redux Store:
// src/App.js import React from 'react'; import { Provider } from 'react-redux'; import store from './store'; import ToDoList from './components/ToDoList'; function App() { return ( <Provider store={store}> <div className="App"> <ToDoList /> </div> </Provider> ); } export default App;這樣就完成了一個簡單的 ToDo List 應用程式,並且使用了 React Redux 來管理狀態。希望這份範例能對你有所幫助!如果你有任何問題或需要進一步的說明,請隨時告訴我。
照著上面的的資料夾結構與範例程式碼複製貼上之後,真的可以跑出一個很陽春的TODO List
不過對於語法相當的輕描淡寫帶過
我們請ChatGPT在補上TypeScript的同時,也對語法做更詳細的解析

當然,我會為你提供使用 TypeScript 的 ToDo List 範例,並提供語法講解。
首先,讓我們來更新之前的程式碼以適應 TypeScript。
步驟 1:建立 Redux Store
首先,確保你的應用程式已經安裝了 Redux 和 React Redux。然後,建立 Redux Store 的程式碼:
// src/store.ts import { createStore } from 'redux'; import rootReducer from './reducers'; const store = createStore(rootReducer); export default store;在這個例子中,我們使用了
.ts擴展名,表示這是 TypeScript 檔案。步驟 2:建立 Reducer
更新 ToDo Reducer 以支援 TypeScript。在
src/reducers/todoReducer.ts中的程式碼如下:// src/reducers/todoReducer.ts interface Todo { id: number; text: string; } interface TodoState { todos: Todo[]; } const initialState: TodoState = { todos: [], }; type TodoAction = { type: 'ADD_TODO'; payload: Todo } | { type: 'REMOVE_TODO'; payload: number }; const todoReducer = (state = initialState, action: TodoAction): TodoState => { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [...state.todos, action.payload], }; case 'REMOVE_TODO': return { ...state, todos: state.todos.filter((todo) => todo.id !== action.payload), }; default: return state; } }; export default todoReducer;在這裡,我們使用 TypeScript 定義了
Todo和TodoState介面,以及TodoAction類型,以確保我們的 Reducer 遵循正確的類型。步驟 3:建立 Action Creators
更新 Action Creators 以支援 TypeScript。在
src/actions/todoActions.ts中的程式碼如下:// src/actions/todoActions.ts interface Todo { id: number; text: string; } export const addTodo = (todo: Todo) => { return { type: 'ADD_TODO', payload: todo, }; }; export const removeTodo = (id: number) => { return { type: 'REMOVE_TODO', payload: id, }; };在這裡,我們使用
Todo介面來定義 Action 的payload。步驟 4:建立 ToDoList 元件
更新 ToDoList 元件以支援 TypeScript。在
src/components/ToDoList.tsx中的程式碼如下:// src/components/ToDoList.tsx import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { addTodo, removeTodo } from '../actions/todoActions'; import { RootState } from '../reducers/rootReducer'; // 引入根 Reducer 的類型 const ToDoList: React.FC = () => { const [input, setInput] = useState<string>(''); const todos = useSelector((state: RootState) => state.todoReducer.todos); // 使用 RootState 來確保正確的類型 const dispatch = useDispatch(); const handleAddTodo = () => { dispatch(addTodo({ id: Date.now(), text: input })); setInput(''); }; const handleRemoveTodo = (id: number) => { dispatch(removeTodo(id)); }; return ( <div> <h2>ToDo List</h2> <div> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={handleAddTodo}>Add</button> </div> <ul> {todos.map((todo) => ( <li key={todo.id}> {todo.text} <button onClick={() => handleRemoveTodo(todo.id)}>Remove</button> </li> ))} </ul> </div> ); }; export default ToDoList;在這裡,我們使用了
React.FC類型定義 ToDoList 元件,同時也使用了RootState來確保正確的類型。步驟 5:使用 Provider 連接應用程式
最後,確保你的應用程式的最上層使用 Provider 來連接 Redux Store,這部分的程式碼不需要修改。
現在,你的 ToDo List 應用程式已經使用 TypeScript 進行了更新。這能確保你的程式在編譯時檢測到類型錯誤,有助於提高程式碼的可靠性。
希望這份 TypeScript 版的範例和說明對你有所幫助!如果你有任何問題或需要進一步的說明,請隨時告訴我。
變成TypeScript了,但是還是沒有講解多少
有疑惑的還是直接問比較快
useSelector和useDispatch是 React Redux 提供的兩個 hook,它們用於在 functional component 中使用 Redux。
useSelector
useSelector是一個用來從 Redux Store 中選擇(或者說提取)狀態的 hook。它接收一個函式作為參數,這個函式接收整個 Redux State 作為參數,並返回你想要的狀態。舉例來說,如果你的 Redux State 包含一個
counter的狀態,你可以透過useSelector來選擇這個狀態:import { useSelector } from 'react-redux'; const CounterComponent = () => { const counter = useSelector(state => state.counter); return <div>Counter: {counter}</div>; };這個例子中,
useSelector會返回state.counter的值,然後你可以在組件中使用這個值。
useDispatch
useDispatch是一個用來發送 Action 到 Redux Store 的 hook。它返回一個 dispatch 函式,你可以使用這個函式來派發 Action。舉例來說,如果你有一個 Action Creator 叫做
increment,你可以透過useDispatch來派發這個 Action:import { useDispatch } from 'react-redux'; import { increment } from './actions'; const CounterComponent = () => { const dispatch = useDispatch(); const handleIncrement = () => { dispatch(increment()); }; return ( <div> <button onClick={handleIncrement}>Increment</button> </div> ); };在這個例子中,當按下按鈕時,
handleIncrement會派發incrementAction 到 Redux Store。簡單來說,
useSelector用來選擇狀態,而useDispatch用來派發 Action 到 Redux Store。希望這能幫助你理解這兩個 hook 的作用!如果你有任何進一步的問題,請隨時告訴我。
學習完新語法,再把這些語法以及名詞的關係畫一個流程圖看看
當然,我可以使用 Mermaid 來簡單表示
useSelector、useDispatch、Reducer 和 Action 之間的運作流程。以下是一個簡單的流程圖:graph TD A[Component] -->|使用 useSelector| B[Redux Store] A -->|使用 useDispatch| C[Redux Store] C -->|dispatch| D[Reducer] D -->|處理 Action| E[新的狀態] E -->|返回新的狀態| B在這個流程圖中,我們有以下幾個元素:
- Component (元件):React 元件,使用
useSelector和useDispatch來訪問 Redux Store 和派發 Action。- Redux Store (Redux 儲存庫):Redux 中的儲存庫,存放整個應用程式的狀態。
- Reducer (處理器):處理 Action,根據 Action 來修改應用程式的狀態。
- Action (行動):描述發生了什麼事件,使用
useDispatch來派發到 Reducer。- 新的狀態:處理後的狀態,由 Reducer 返回並存放在 Redux Store 中。
流程如下:
- React 元件(Component)使用
useSelector來選擇需要的狀態從 Redux Store 中提取。- React 元件(Component)使用
useDispatch來派發 Action 到 Redux Store。- 派發的 Action 進入 Reducer(Reducer)處理。
- Reducer 根據 Action 的類型,修改應用程式的狀態。
- Reducer 返回新的狀態,並存放在 Redux Store 中。
這個流程圖簡單地展示了 React Redux 中
useSelector、useDispatch、Reducer 和 Action 之間的互動關係。希望這能幫助你更好地理解它們之間的運作方式!如果你有任何進一步的問題,請隨時告訴我。


當然,我可以提供一個不使用 Redux 的 ToDo List 範例,並與之前的使用 Redux 的範例進行比較,讓你了解兩者的優劣。
不使用 Redux 的 ToDo List 範例
以下是一個不使用 Redux 的簡單 ToDo List 範例:
import React, { useState } from 'react'; const ToDoList = () => { const [input, setInput] = useState(''); const [todos, setTodos] = useState([]); const handleAddTodo = () => { if (input.trim() !== '') { setTodos([...todos, { id: Date.now(), text: input }]); setInput(''); } }; const handleRemoveTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)); }; return ( <div> <h2>ToDo List</h2> <div> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={handleAddTodo}>Add</button> </div> <ul> {todos.map(todo => ( <li key={todo.id}> {todo.text} <button onClick={() => handleRemoveTodo(todo.id)}>Remove</button> </li> ))} </ul> </div> ); }; export default ToDoList;在這個範例中,我們使用了 React 的
useStatehook 來管理狀態,而不是 Redux。狀態(todos)和操作(新增、刪除)都被包含在同一個元件中。比較優劣
使用 Redux 的優點:
- 統一的狀態管理: Redux 提供一個統一的 Store 來管理應用程式的狀態,這使得狀態更容易追蹤和管理,特別在大型應用程式中。
- 可預測性: Redux 的單向數據流和不可變的狀態修改使應用程式的行為更可預測,有助於減少錯誤。
- 方便的開發工具: Redux 提供了強大的開發工具,可以輕鬆追蹤狀態變化、偵錯和時間旅行式除錯。
- 跨元件共享狀態: Redux 允許多個元件輕鬆共享相同的狀態,而不需要通過 props 進行深層傳遞。
不使用 Redux 的優點:
- 簡單性: 不使用 Redux 可以使代碼更簡單,特別是對於小型應用程式或簡單的元件來說,可以減少額外的 Redux 相關代碼。
- 學習曲線: 如果你是初學者,學習 React 和 Redux 可能會有一定的學習曲線。不使用 Redux 可以讓你更快地開始建立應用程式。
總之,使用 Redux 的主要優勢在於管理複雜的狀態和提供開發工具,特別適用於大型應用程式。但對於小型應用程式或初學者來說,不使用 Redux 可能更簡單和快速上手。
選擇是否使用 Redux 取決於你的專案需求和個人偏好。對於大型應用程式,或需要高度可預測性和測試性的專案,Redux 可能是一個不錯的選擇。對於小型專案或初學者,不使用 Redux 也可以是一個合理的選擇。

在撰寫 Redux 中的 Reducer 時,有一些重要的原則需要遵守,以確保程式碼的正確性和可維護性:
原則 1:純函式 (Pure Function)
正確示例:
const reducer = (state, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } };錯誤示例:
let count = 0; const reducer = (action) => { switch (action.type) { case 'INCREMENT': count++; return count; case 'DECREMENT': count--; return count; default: return count; } };說明: Reducer 必須是純函式,這意味著它的執行結果必須完全由輸入決定,不得有副作用。純函式不會改變傳入的參數,也不會改變其他地方的狀態或資料。
原則 2:不得修改傳入的 state
正確示例:
const reducer = (state, action) => { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [...state.todos, action.payload] }; case 'REMOVE_TODO': return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload) }; default: return state; } };錯誤示例:
const reducer = (state, action) => { switch (action.type) { case 'ADD_TODO': state.todos.push(action.payload); // 修改了傳入的 state return state; case 'REMOVE_TODO': state.todos = state.todos.filter(todo => todo.id !== action.payload); // 修改了傳入的 state return state; default: return state; } };說明: Reducer 不得直接修改傳入的 state,應該返回一個新的狀態物件。這保證了 Redux 的單向數據流。
原則 3:返回一個新的狀態物件
正確示例:
const reducer = (state, action) => { switch (action.type) { case 'SET_USER': return { ...state, user: action.payload }; default: return state; } };錯誤示例:
const reducer = (state, action) => { switch (action.type) { case 'SET_USER': state.user = action.payload; // 修改了原始狀態 return state; default: return state; } };說明: Reducer 應該總是返回一個全新的狀態物件,而不是修改原始狀態。
原則 4:預設返回原始狀態
正確示例:
const reducer = (state = initialState, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } };說明: 在 Reducer 中,應該總是返回一個合理的狀態。如果沒有匹配到任何 action type,則應該返回原始狀態。
這些原則是為了確保 Redux 應用程式的正確運作和易於維護。遵循這些原則能幫助你寫出高質量的 Redux 代碼。如果你有任何問題或需要進一步的說明,請隨時告訴我。

在這次的對話中,我們探討了以下主題:
- React Redux:React Redux 是一個結合了 React 和 Redux 的庫,用於更有效地管理 React 應用程式的狀態。它提供了兩個主要的元件,
Provider和connect,讓 React 元件能夠訪問 Redux Store。useSelector和useDispatch:這兩個是 React Redux 提供的 hook,用於在 functional component 中使用 Redux。useSelector用於選擇狀態,useDispatch用於派發 Action 到 Redux Store。- Reducer 的原則:
- 純函式 (Pure Function):Reducer 必須是純函式,執行結果完全由輸入決定,不得有副作用。
- 不得修改傳入的 state:Reducer 不得直接修改傳入的 state,應該返回一個新的狀態物件。
- 返回一個新的狀態物件:Reducer 應該總是返回一個全新的狀態物件,而不是修改原始狀態。
- 預設返回原始狀態:Reducer 應該總是返回一個合理的狀態,如果沒有匹配到任何 action type,則應該返回原始狀態。
總結來說,React Redux 是一個強大的工具,可以幫助我們更有效地管理 React 應用程式的狀態。
useSelector和useDispatch讓我們在 functional component 中更容易地使用 Redux。在編寫 Reducer 時,我們需要遵守一些重要的原則,如保持它們是純函式、不修改傳入的 state,並始終返回一個新的狀態物件。選擇是否使用 Redux 取決於專案需求和個人偏好。Redux 適用於大型、複雜的應用程式,提供了一個統一的狀態管理解決方案。然而,對於小型應用程式或初學者來說,可能會覺得不使用 Redux 更簡單和直觀。
無論選擇了哪種方法,重要的是理解這些概念並遵循最佳實踐,以確保你的應用程式能夠保持可靠和易於維護。如果你有任何進一步的問題或需要協助,請隨時告訴我!
本次的對話內容記錄在這裡:
https://chat.openai.com/share/e64a60e8-ae89-40fa-847f-77e6616eaf16
redux看起來真的會比較複雜,因此適合用在大型複雜的專案中
也尤其那種State要傳好幾個Component、好多種動作需要更改同一種state內容的
定義Reducer和action 、還要寫action creator等等的程序看起來很繁雜
但下一章節會介紹 Redux Toolkit,官方推出的toolkit
能夠用更簡易且整合的方式來定義reducer與action
但了解Redux基本概念與流程,reducer與action和store的關係是重要的
我是redux放一篇介紹放不完的前端工程師一宵三筵,我們明天見QQ