createContext
、useContext
和 useReducer
的組合是 React 中管理全局狀態的一種常見模式。這種模式非常適合在不引入第三方狀態管理庫(如 Redux)的情況下,管理復雜的全局狀態。
以下是一個經典的例子,展示如何使用 createContext
、useContext
和 useReducer
來實現一個簡單的全局狀態管理。
示例:Todo 應用
我們將實現一個簡單的 Todo 應用,支持以下功能:
- 添加任務
- 刪除任務
- 切換任務完成狀態
1. 定義全局狀態和操作
TodoContext.tsx
import React, { createContext, useReducer, useContext, ReactNode } from 'react';// 定義 Todo 項的類型
interface Todo {id: number;text: string;completed: boolean;
}// 定義全局狀態的類型
interface TodoState {todos: Todo[];
}// 定義操作類型
type TodoAction =| { type: 'ADD_TODO'; payload: string }| { type: 'TOGGLE_TODO'; payload: number }| { type: 'DELETE_TODO'; payload: number };// 定義初始狀態
const initialState: TodoState = {todos: [],
};// 定義 reducer 函數
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {switch (action.type) {case 'ADD_TODO':return {...state,todos: [...state.todos,{ id: Date.now(), text: action.payload, completed: false },],};case 'TOGGLE_TODO':return {...state,todos: state.todos.map((todo) =>todo.id === action.payload? { ...todo, completed: !todo.completed }: todo),};case 'DELETE_TODO':return {...state,todos: state.todos.filter((todo) => todo.id !== action.payload),};default:return state;}
};// 創建 Context
const TodoContext = createContext<{state: TodoState;dispatch: React.Dispatch<TodoAction>;
} | null>(null);// 創建 Provider 組件
export const TodoProvider = ({ children }: { children: ReactNode }) => {const [state, dispatch] = useReducer(todoReducer, initialState);return (<TodoContext.Provider value={{ state, dispatch }}>{children}</TodoContext.Provider>);
};// 自定義 Hook,用于使用 TodoContext
export const useTodoContext = () => {const context = useContext(TodoContext);if (!context) {throw new Error('useTodoContext must be used within a TodoProvider');}return context;
};
2. 使用全局狀態
App.tsx
import React, { useState } from 'react';
import { TodoProvider, useTodoContext } from './TodoContext';const TodoList = () => {const { state, dispatch } = useTodoContext();return (<div><h2>Todo List</h2><ul>{state.todos.map((todo) => (<li key={todo.id}><spanstyle={{textDecoration: todo.completed ? 'line-through' : 'none',cursor: 'pointer',}}onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>{todo.text}</span><button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>Delete</button></li>))}</ul></div>);
};const AddTodo = () => {const { dispatch } = useTodoContext();const [text, setText] = useState('');const handleAddTodo = () => {if (text.trim()) {dispatch({ type: 'ADD_TODO', payload: text });setText('');}};return (<div><inputtype="text"value={text}onChange={(e) => setText(e.target.value)}placeholder="Add a new task"/><button onClick={handleAddTodo}>Add</button></div>);
};const App = () => {return (<TodoProvider><h1>Todo App</h1><AddTodo /><TodoList /></TodoProvider>);
};export default App;
3. 代碼解釋
- TodoContext:
- 使用 createContext 創建一個全局狀態的上下文。
- 使用 useReducer 管理全局狀態和操作。
- TodoProvider:
- 包裹應用的根組件,提供全局狀態和 dispatch 方法。
- useTodoContext:
- 自定義 Hook,用于簡化 useContext 的使用,并確保上下文只能在 TodoProvider 內部使用。
- todoReducer:
- 定義了如何根據不同的操作(ADD_TODO、TOGGLE_TODO、DELETE_TODO)更新全局狀態。
- 組件分離:
- AddTodo 組件負責添加任務。
- TodoList 組件負責顯示任務列表,并支持切換任務狀態和刪除任務。
4. 優勢
- 清晰的狀態管理:
- 使用 useReducer 將狀態更新邏輯集中在一個地方,便于維護和擴展。
- 全局狀態共享:
- 使用 createContext 和 useContext 實現全局狀態共享,無需手動傳遞 props。
- 組件解耦:
- 通過上下文和 dispatch,各組件可以獨立處理自己的邏輯,而無需直接依賴其他組件。
5. 總結
createContext
+ useContext
+ useReducer
是一種輕量級的全局狀態管理方案,適合中小型項目。它的核心思想是:
- 使用
createContext
提供全局狀態。 - 使用
useReducer
管理狀態更新邏輯。 - 使用
useContext
在組件中訪問和操作全局狀態。