useContext与useReducer #
一、useContext深入 #
1.1 Context基础 #
Context 提供了一种在组件树中共享数据的方式,无需手动传递 props。
javascript
import { createContext, useContext } from 'react';
// 创建Context
const ThemeContext = createContext('light');
// Provider组件
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 消费Context
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Button</button>;
}
1.2 Context层级结构 #
text
┌─────────────────────────────────────────────────────┐
│ App │
│ ┌───────────────────────────────────────────────┐ │
│ │ ThemeContext.Provider │ │
│ │ value="dark" │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ Toolbar │ │ │
│ │ │ ┌───────────────────────────────────┐ │ │ │
│ │ │ │ Button │ │ │ │
│ │ │ │ useContext(ThemeContext) → "dark"│ │ │ │
│ │ │ └───────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
1.3 动态Context #
javascript
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
当前主题: {theme}
</button>
);
}
1.4 默认值 #
javascript
// 默认值在没有匹配Provider时使用
const UserContext = createContext({
user: null,
setUser: () => {}
});
// 组件在Provider外部使用时获得默认值
function Profile() {
const { user } = useContext(UserContext);
if (!user) {
return <div>请登录</div>;
}
return <div>欢迎, {user.name}</div>;
}
1.5 多个Context #
javascript
const ThemeContext = createContext('light');
const UserContext = createContext(null);
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
const { user, setUser } = useContext(UserContext);
return (
<header className={theme}>
{user ? (
<span>{user.name}</span>
) : (
<button onClick={() => setUser({ name: 'Guest' })}>登录</button>
)}
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</header>
);
}
二、useReducer深入 #
2.1 Reducer模式 #
javascript
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
);
}
2.2 Action模式 #
javascript
// 简单Action
dispatch({ type: 'increment' });
// 带Payload的Action
dispatch({ type: 'set', payload: 10 });
// 复杂Payload
dispatch({
type: 'update_user',
payload: {
name: 'Alice',
email: 'alice@example.com'
}
});
2.3 复杂状态管理 #
javascript
const initialState = {
todos: [],
filter: 'all',
loading: false,
error: null
};
function todoReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, loading: false, todos: action.payload };
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.payload };
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)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
default:
return state;
}
}
2.4 惰性初始化 #
javascript
function init(initialState) {
return {
...initialState,
todos: JSON.parse(localStorage.getItem('todos') || '[]')
};
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState, init);
// ...
}
三、组合使用 #
3.1 基本组合模式 #
javascript
const StateContext = createContext();
const DispatchContext = createContext();
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<TodoList />
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 自定义Hook获取状态
function useTodoState() {
const context = useContext(StateContext);
if (context === undefined) {
throw new Error('useTodoState must be used within Provider');
}
return context;
}
// 自定义Hook获取dispatch
function useTodoDispatch() {
const context = useContext(DispatchContext);
if (context === undefined) {
throw new Error('useTodoDispatch must be used within Provider');
}
return context;
}
// 使用
function TodoItem({ todo }) {
const state = useTodoState();
const dispatch = useTodoDispatch();
return (
<div>
<span>{todo.text}</span>
<button onClick={() => dispatch({ type: 'toggle', payload: todo.id })}>
{todo.completed ? '取消' : '完成'}
</button>
</div>
);
}
3.2 完整示例:Todo应用 #
javascript
const TodoContext = createContext();
const initialState = {
todos: [],
filter: 'all'
};
function todoReducer(state, action) {
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)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
default:
return state;
}
}
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(todoReducer, initialState);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
}
function useTodo() {
const context = useContext(TodoContext);
if (!context) {
throw new Error('useTodo must be used within TodoProvider');
}
return context;
}
function TodoApp() {
return (
<TodoProvider>
<TodoHeader />
<TodoList />
<TodoFooter />
</TodoProvider>
);
}
function TodoHeader() {
const { dispatch } = useTodo();
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: 'ADD_TODO', payload: text });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="添加任务"
/>
<button type="submit">添加</button>
</form>
);
}
function TodoList() {
const { state } = useTodo();
const filteredTodos = state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
});
return (
<ul>
{filteredTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
function TodoItem({ todo }) {
const { dispatch } = useTodo();
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
删除
</button>
</li>
);
}
function TodoFooter() {
const { state, dispatch } = useTodo();
const remaining = state.todos.filter(t => !t.completed).length;
return (
<div>
<span>{remaining} 项未完成</span>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}>
全部
</button>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}>
未完成
</button>
<button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'completed' })}>
已完成
</button>
</div>
);
}
四、最佳实践 #
4.1 分离Context #
javascript
// ✅ 分离状态和dispatch
const StateContext = createContext();
const DispatchContext = createContext();
// 避免不必要的重新渲染
function Display() {
const state = useContext(StateContext);
return <div>{state.count}</div>;
}
function Controls() {
const dispatch = useContext(DispatchContext);
return <button onClick={() => dispatch({ type: 'increment' })}>+</button>;
}
4.2 自定义Hook封装 #
javascript
function useTodoContext() {
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);
const addTodo = useCallback((text) => {
dispatch({ type: 'ADD_TODO', payload: text });
}, [dispatch]);
const toggleTodo = useCallback((id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
}, [dispatch]);
const deleteTodo = useCallback((id) => {
dispatch({ type: 'DELETE_TODO', payload: id });
}, [dispatch]);
return {
...state,
addTodo,
toggleTodo,
deleteTodo
};
}
4.3 类型安全(TypeScript) #
typescript
type State = {
todos: Todo[];
filter: FilterType;
};
type Action =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: number }
| { type: 'DELETE_TODO'; payload: number }
| { type: 'SET_FILTER'; payload: FilterType };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false }]
};
// ...
}
}
五、性能优化 #
5.1 避免不必要的渲染 #
javascript
// 使用React.memo
const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</li>
);
});
5.2 拆分Context #
javascript
// 拆分为多个Context
const TodosContext = createContext([]);
const FilterContext = createContext('all');
function TodoList() {
const todos = useContext(TodosContext);
const filter = useContext(FilterContext);
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
return <ul>{filteredTodos.map(todo => <TodoItem key={todo.id} todo={todo} />)}</ul>;
}
六、总结 #
| 要点 | 说明 |
|---|---|
| useContext | 访问Context值 |
| useReducer | 复杂状态管理 |
| 组合使用 | 实现全局状态管理 |
| 分离Context | 优化渲染性能 |
核心原则:
- 使用 Context 避免 prop drilling
- 使用 useReducer 管理复杂状态
- 分离状态和 dispatch 优化性能
- 封装自定义 Hook 提供更好的 API
最后更新:2026-03-26