React-从useContext 到Reducer + Context状态管理模式

18 阅读3分钟

前言

在 React 开发中,随着应用规模的扩大,Props 多层向下传递和状态逻辑复杂化是两大核心痛点。本文将详细讲解如何利用 useContext 实现跨层级通信,以及如何通过 useReducerContext 的结合,构建一套无需第三方库的轻量级全局状态管理方案

一、 useContext:打破层级枷锁

1. 产生背景

当组件需要跨越多个嵌套层级传递数据时,通过 Props 一层层手动传递不仅麻烦,而且难以维护。useContext 提供了一种“广播”机制,让深层后代组件能直接触达上层数据。

2. 使用三部曲

  1. 首先通过createContex创建一个上下文对象,可以选择传入一个默认值

    import { createContext } from 'react';
    const ThemeContext = createContext<ThemeContextType | null>(null);
    
  2. 接下来,在上层组件中,用创建的 ThemeContext.Provider包裹子组件,再将需要共享的数据通过 value属性传递下去。

    const [theme, setTheme] = useState("dark");
    return ( 
        <ThemeContext.Provider value={{ theme, setTheme }}> 
            {children}      
        </ThemeContext.Provider> 
    );
    
  3. 在子组件中使用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 使用。