目前這個系列已挑選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
會派發increment
Action 到 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 的
useState
hook 來管理狀態,而不是 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