前言
在 React 开发中,随着应用规模的扩大,Props 多层向下传递和状态逻辑复杂化是两大核心痛点。本文将详细讲解如何利用 useContext 实现跨层级通信,以及如何通过 useReducer 与 Context 的结合,构建一套无需第三方库的轻量级全局状态管理方案。
一、 useContext:打破层级枷锁
1. 产生背景
当组件需要跨越多个嵌套层级传递数据时,通过 Props 一层层手动传递不仅麻烦,而且难以维护。useContext 提供了一种“广播”机制,让深层后代组件能直接触达上层数据。
2. 使用三部曲
-
首先通过
createContex创建一个上下文对象,可以选择传入一个默认值import { createContext } from 'react'; const ThemeContext = createContext<ThemeContextType | null>(null); -
接下来,在上层组件中,用创建的
ThemeContext.Provider包裹子组件,再将需要共享的数据通过value属性传递下去。const [theme, setTheme] = useState("dark"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); -
在子组件中使用useContext来获传递过来的value值
import { useContext } from 'react'; const ThemedButton: React.FC = () => { // 直接获取主题值 const theme = useContext(ThemeContext); //dark return <button className={theme}>Themed Button</button>; }
3. 使用示例:
import React, { createContext, useContext, useState, ReactNode } from 'react';
// 1. 创建上下文对象,并定义类型
interface ThemeContextType {
theme: string;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
// 2. 提供者组件 (Provider)
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<string>("light");
const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 3. 消费者组件 (Consumer)
const ThemedButton: React.FC = () => {
// 使用 useContext 直接获取,无需 Props
const context = useContext(ThemeContext);
if (!context) return null;
return (
<button
style={{ backgroundColor: context.theme === 'light' ? '#fff' : '#333' }}
onClick={context.toggleTheme}
>
当前主题:{context.theme}
</button>
);
};
export default ThemedButton;
二、 终极方案:useReducer + useContext
1. 概念与优势
将 useReducer 的逻辑解耦能力与 useContext 的跨层传递能力结合,可以实现一个类似于 Redux 的轻量级全局状态管理。
- 适用场景:中小型应用,状态逻辑较复杂,但不想引入重型状态管理库。
2. 实战步骤
第一步:定义上下文与类型
使用createContext创建一个Content
import { createContext, Dispatch } from "react";
export interface Todo {
id: number;
text: string;
}
// 定义 Action 类型
type TodoAction =
| { type: 'ADD_TODO'; text: string }
| { type: 'REMOVE_TODO'; id: number };
// 定义 Context 包含的内容
interface TodoContextType {
todos: Todo[];
dispatch: Dispatch<TodoAction>;
}
export const TodoContext = createContext<TodoContextType | null>(null);
第二步:实现 Reducer 逻辑
定义相关的Reducer与action操作
export const initialTasks: Todo[] = [];
export function todoReducer(state: Todo[], action: TodoAction): Todo[] {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text }];
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
第三步:顶层包裹 (Provider)
在应用的顶层组件中,使用userReducer获取状态与派发函数
import React, { useReducer } from 'react';
import { TodoContext } from './TodoContext';
import { todoReducer, initialTasks } from './todoReducer';
const App: React.FC = () => {
// 在顶层维护状态和派发函数
const [todos, dispatch] = useReducer(todoReducer, initialTasks);
return (
<TodoContext.Provider value={{ todos, dispatch }}>
<div className="App">
<h1>我的待办清单</h1>
<TodoInput />
<TodoList />
</div>
</TodoContext.Provider>
);
};
export default App;
第四步:后代组件消费
import React, { useState, useContext } from 'react';
import { TodoContext } from './TodoContext';
const TodoInput: React.FC = () => {
const [text, setText] = useState('');
const context = useContext(TodoContext);
// 容错处理
if (!context) return null;
const { dispatch } = context;
const handleSubmit = () => {
if (text.trim()) {
dispatch({ type: 'ADD_TODO', text: text.trim() });
setText('');
}
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleSubmit}>添加任务</button>
</div>
);
};
export default TodoInput;
三、 使用注意点
- 渲染抖动:当 Context 的
value发生变化时,所有调用了useContext的组件都会重新渲染。 - 优化方案:尽量将不常变动的数据和频繁变动的数据拆分到不同的 Context 中,或者在子组件配合
React.memo使用。