createContext
、useContext
和 useReducer
的组合是 React 中管理全局状态的一种常见模式。这种模式非常适合在不引入第三方状态管理库(如 Redux)的情况下,管理复杂的全局状态。
以下是一个经典的例子,展示如何使用 createContext
、useContext
和 useReducer
来实现一个简单的全局状态管理。
示例:Todo 应用
我们将实现一个简单的 Todo 应用,支持以下功能:
- 添加任务
- 删除任务
- 切换任务完成状态
1. 定义全局状态和操作
TodoContext.tsx
javascript
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
javascript
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}>
<span
style={{
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>
<input
type="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
在组件中访问和操作全局状态。