不用 Redux 也能全局状态管理?看我用 useReducer+Context 搞个 Todo 应用

写 React 应用时,你是不是经常纠结:状态该放在哪个组件?深层子组件需要用某个状态,难道要一层一层传 props?遇到复杂状态(比如 Todo 列表的增删改查),用 useState 感觉力不从心,又不想引入 Redux 这种 "重型武器"------ 别愁了,用useReducer + Context组合就能优雅解决这些问题!

一、项目架构设计:为什么选择 useReducer+Context?

(1)传统方案的痛点

  • 状态提升:把状态放在共同的父组件,然后通过 props 一层一层传给子组件。问题是:如果组件层级很深,中间层会有大量 "不必要的 props 传递",代码冗余且难维护。
  • useState:在每个组件里单独管理状态。问题是:如果多个组件需要共享状态(比如 Todo 列表),状态同步会变得复杂。

(2)useReducer+Context 的优势

  • useReducer:像一个 "状态管理专家",把复杂的状态修改逻辑集中在一个纯函数里,避免状态修改逻辑散落在各个组件中。
  • Context:实现 "跨层级状态共享",让任何层级的组件都能直接获取状态,无需层层传递 props。

这两者结合,既能高效管理状态,又能简化组件间通信,非常适合中小型应用的全局状态管理。

二、核心模块拆解:自定义 hook+Context,打造全局状态管理系统

(1)第一步:创建 Context,作为状态的 "管道"

javascript 复制代码
// TodoContext.js
import { createContext } from 'react';

// 创建一个Context对象,初始值设为null
export const TodoContext = createContext(null);

Context 就像一个 "全局管道",所有组件都可以通过它获取状态和修改状态的方法。

(2)第二步:创建 reducer,定义状态修改规则

javascript 复制代码
// reducers/todoReducer.js
// 纯函数:根据action修改状态
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, {
        id: Date.now(), // 用时间戳生成唯一ID
        text: action.text,
        done: false
      }];
    case 'TOGGLE_TODO':
      return state.map(todo => 
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'REMOVE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

export default todoReducer;

这个 reducer 定义了 Todo 应用的三种操作:添加、切换完成状态、删除。注意它是纯函数,不修改原状态,而是返回新状态。

(3)第三步:封装自定义 hook,整合 useReducer 和 Context

javascript 复制代码
// hooks/useTodos.js
import { useReducer } from 'react';
import todoReducer from '../reducers/todoReducer';

// 自定义hook:封装Todo状态管理逻辑
export function useTodos(initial = []) {
  const [todos, dispatch] = useReducer(todoReducer, initial);

  // 封装action creators,简化组件中的调用
  const addTodo = text => dispatch({ type: 'ADD_TODO', text });
  const toggleTodo = id => dispatch({ type: 'TOGGLE_TODO', id });
  const removeTodo = id => dispatch({ type: 'REMOVE_TODO', id });

  // 返回状态和操作方法
  return {
    todos,
    addTodo,
    toggleTodo,
    removeTodo,
  };
}

这个自定义 hook 做了两件事:

  1. useReducer初始化 Todo 状态和 dispatch 函数;
  2. 封装了三个操作函数(addTodotoggleTodoremoveTodo),让组件使用更方便。

(4)第四步:创建 Context 提供者,让状态 "流动" 起来

jsx 复制代码
// App.jsx
import { TodoContext } from './TodoContext';
import { useTodos } from './hooks/useTodos';
import AddTodo from './components/AddTodo';
import TodoList from './components/TodoList';

function App() {
  // 初始化Todo状态
  const todosHook = useTodos([]);

  return (
    // 用Context.Provider包裹组件树,提供状态
    <TodoContext.Provider value={todosHook}>
      <h1>Todo App</h1>
      <AddTodo />
      <TodoList />
    </TodoContext.Provider>
  );
}

export default App;

这里的关键点是:把useTodos返回的状态和方法通过TodoContext.Providervalue属性提供给所有子组件。

(5)第五步:在组件中使用全局状态

jsx 复制代码
// components/AddTodo.jsx
import { useState } from 'react';
import { useTodoContext } from '../hooks/useTodoContext';

const AddTodo = () => {
  const [text, setText] = useState('');
  // 通过自定义hook获取全局状态和方法
  const { addTodo } = useTodoContext();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      addTodo(text.trim()); // 调用全局方法添加Todo
      setText(''); // 清空输入框
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
      <button type="submit">Add</button>
    </form>
  );
};

export default AddTodo;
jsx 复制代码
// components/TodoList.jsx
import { useTodoContext } from '../hooks/useTodoContext';

const TodoList = () => {
  // 通过自定义hook获取全局状态和方法
  const { todos, toggleTodo, removeTodo } = useTodoContext();

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <span
            onClick={() => toggleTodo(todo.id)}
            style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
          >
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>remove</button>
        </li>
      ))}
    </ul>
  );
};

export default TodoList;

注意这两个组件:它们完全不依赖 props 传递,而是通过useTodoContext直接获取全局状态和操作方法。这样无论组件嵌套多深,都能轻松访问全局状态。

三、项目亮点解析:这些设计细节让代码更优雅

(1)自定义 hook 封装状态逻辑,组件只关心渲染

useTodos这个自定义 hook 把 Todo 相关的状态逻辑(初始化、增删改查)全部封装起来,组件只需要调用它提供的方法,不需要关心内部实现。这符合 "关注点分离" 原则,让组件代码更简洁。

(2)统一的状态修改入口,保证状态一致性

所有状态修改都通过todoReducer,这确保了状态修改逻辑集中且统一。如果需要修改 "添加 Todo" 的逻辑(比如增加验证),只需要改一处代码。

(3)避免重复的 Context 导入,简化组件代码

封装了useTodoContext这个自定义 hook,组件里不需要每次都写useContext(TodoContext),减少重复代码:

javascript 复制代码
// hooks/useTodoContext.js
import { TodoContext } from "../TodoContext";
import { useContext } from 'react';

export function useTodoContext() {
  return useContext(TodoContext);
}

四、扩展思考:这个架构还能怎么优化?

(1)添加持久化存储

当前 Todo 数据只存在内存中,页面刷新就会丢失。可以用localStorage实现持久化:

javascript 复制代码
// hooks/useTodos.js
import { useReducer, useEffect } from 'react';
import todoReducer from '../reducers/todoReducer';

export function useTodos() {
  // 从localStorage读取初始值
  const initialTodos = JSON.parse(localStorage.getItem('todos')) || [];
  
  const [todos, dispatch] = useReducer(todoReducer, initialTodos);

  // 监听todos变化,保存到localStorage
  useEffect(() => {
    localStorage.setItem('todos', JSON.stringify(todos));
  }, [todos]);

  // ...其余代码不变
}

(2)处理异步操作

如果需要处理异步操作(比如从 API 获取 Todo 数据),可以在自定义 hook 里添加useEffect

javascript 复制代码
// hooks/useTodos.js
export function useTodos() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [loading, setLoading] = useState(true);

  // 初始化时从API获取数据
  useEffect(() => {
    fetch('/api/todos')
      .then(res => res.json())
      .then(data => {
        dispatch({ type: 'INIT_TODOS', payload: data });
        setLoading(false);
      });
  }, []);

  // ...其余代码不变
}

五、总结:useReducer+Context 组合的核心价值

  1. 状态管理集中化:用 reducer 统一管理状态修改逻辑,避免逻辑分散。
  2. 跨层级通信简单化:通过 Context 直接获取状态,无需 props 层层传递。
  3. 代码可维护性提升:自定义 hook 封装状态逻辑,组件只关注渲染,职责清晰。
相关推荐
sunbyte6 分钟前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | NotesApp(便签笔记组件)
前端·javascript·css·vue.js·笔记·tailwindcss
今晚一定早睡10 分钟前
代码随想录-数组-移除元素
前端·javascript·算法
CAD老兵10 分钟前
JavaScript 闭包在 V8 引擎中实现机制与优化策略
前端·javascript
前端的日常10 分钟前
一分钟搞懂this指向
javascript
爱学习的茄子11 分钟前
【建议收藏】2025最全JS事件循环详解:从底层机制到浏览器渲染
前端·javascript·深度学习
前端小嘎11 分钟前
一个非常有用的设计模式——有限状态机
前端·javascript
柒崽11 分钟前
手把手带你搭建一个MCP客户端,小白也能学会
前端
雲墨款哥12 分钟前
一个前端开发者的救赎之路——JS基础回顾(一)
前端·javascript
杨进军13 分钟前
React 实现 useLayoutEffect 与 useEffect
前端·react.js·前端框架
我想说一句14 分钟前
深入解析 React 核心:JSX 本质与 Key 的奥秘
前端·javascript·前端框架