从0死磕全栈第五天:React 使用zustand实现To-Do List项目

代码世界是现实的镜像,状态管理教会我们:真正的控制不在于凝固不变,而在于优雅地引导变化。

这是「从0死磕全栈」系列的第5篇文章,前面我们已经完成了环境搭建、路由配置和基础功能开发。今天,我们将引入一个轻量级但强大的状态管理工具 ------ Zustand,来实现一个完整的 TodoList 应用。


Zustand 简介

Zustand 是一个轻量级、灵活的 React 状态管理库,以极简 API 解决复杂状态共享问题。

  • ✅ 无需 Provider 包裹,直接导入使用
  • ✅ 使用 create 创建 store,通过 useStore 随时随地访问状态
  • ✅ 支持中间件(如持久化、日志)、异步逻辑
  • ✅ 代码简洁易维护,是中小型项目的理想选择

下面我们将用 React + TypeScript + Zustand 实现一个功能完整的 TodoList。


要实现的功能

  1. 添加待办事项

    • 在输入框中输入内容,点击"添加"按钮或按 Enter 键
    • 新事项添加到列表底部
  2. 标记完成/未完成

    • 点击复选框切换完成状态
    • 已完成事项显示删除线样式
  3. 删除事项

    • 点击右侧"删除"按钮移除该事项
  4. 统计信息

    • 显示总事项数和已完成事项数

1. 安装依赖

bash 复制代码
npm install zustand

2. 创建 Zustand Store

ts 复制代码
// store/todoStore.ts
import { create } from 'zustand';

// 定义 Todo 项的类型
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

// 定义 Store 的类型
interface TodoStore {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
  deleteTodo: (id: number) => void;
}

// 创建 Zustand Store
const useTodoStore = create<TodoStore>((set) => ({
  // 初始状态:空数组
  todos: [],

  // 添加新的待办事项
  addTodo: (text: string) =>
    set((state) => ({
      todos: [
        ...state.todos,
        {
          id: Date.now(), // 使用时间戳作为唯一ID
          text,
          completed: false,
        },
      ],
    })),

  // 切换完成状态
  toggleTodo: (id: number) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ),
    })),

  // 删除待办事项
  deleteTodo: (id: number) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    })),
}));

export default useTodoStore;

💡 设计思想 :Zustand 的 set 函数类似于 Java 中的 setter 方法,提供了统一的状态修改入口,提升了代码的封装性和可读性。


进阶:使用 get 获取当前状态

Zustand 还提供了 get 函数,用于获取当前状态。我们可以扩展 store,添加计算逻辑:

ts 复制代码
// store/todoStore.ts(增强版)
interface TodoStore {
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
  deleteTodo: (id: number) => void;
  getIncompleteCount: () => number; // 新增:获取未完成数量
}

const useTodoStore = create<TodoStore>((set, get) => ({
  todos: [],

  addTodo: (text: string) =>
    set((state) => ({
      todos: [
        ...state.todos,
        {
          id: Date.now(),
          text,
          completed: false,
        },
      ],
    })),

  toggleTodo: (id: number) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      ),
    })),

  deleteTodo: (id: number) =>
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    })),

  // 使用 get() 获取当前状态并计算
  getIncompleteCount: () => {
    const todos = get().todos;
    return todos.filter((todo) => !todo.completed).length;
  },
}));

3. 创建 TodoList 组件

tsx 复制代码
// components/TodoList.tsx
import { useState } from 'react';
import useTodoStore from '../store/todoStore';

const TodoList = () => {
  // 从 store 中解构状态和方法
  const { todos, addTodo, toggleTodo, deleteTodo } = useTodoStore();
  const [inputValue, setInputValue] = useState('');

  // 添加待办事项
  const handleAddTodo = () => {
    if (inputValue.trim()) {
      addTodo(inputValue);
      setInputValue(''); // 清空输入框
    }
  };

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto' }}>
      <h1>Todo List</h1>

      {/* 输入框和添加按钮 */}
      <div style={{ display: 'flex', marginBottom: '20px' }}>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="输入待办事项"
          style={{ flex: 1, padding: '8px' }}
          onKeyPress={(e) => e.key === 'Enter' && handleAddTodo()}
        />
        <button
          onClick={handleAddTodo}
          style={{ marginLeft: '10px', padding: '8px 16px' }}
        >
          添加
        </button>
      </div>

      {/* 待办事项列表 */}
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map((todo) => (
          <li
            key={todo.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              marginBottom: '8px',
              textDecoration: todo.completed ? 'line-through' : 'none',
              color: todo.completed ? '#888' : '#000',
            }}
          >
            {/* 复选框 */}
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              style={{ marginRight: '10px' }}
            />

            {/* 文本 */}
            <span style={{ flex: 1 }}>{todo.text}</span>

            {/* 删除按钮 */}
            <button
              onClick={() => deleteTodo(todo.id)}
              style={{
                background: '#ff4444',
                color: 'white',
                border: 'none',
                padding: '4px 8px',
                borderRadius: '4px',
              }}
            >
              删除
            </button>
          </li>
        ))}
      </ul>

      {/* 统计信息 */}
      <div style={{ marginTop: '20px', color: '#666' }}>
        总计: {todos.length} 项 | 已完成: {todos.filter(t => t.completed).length} 项
      </div>
    </div>
  );
};

export default TodoList;

4. 创建 App 组件

tsx 复制代码
// App.tsx
import TodoList from './components/TodoList';

function App() {
  return (
    <div className="App">
      <TodoList />
    </div>
  );
}

export default App;

5. 项目结构说明

bash 复制代码
src/
├── store/
│   └── todoStore.ts        # Zustand 状态管理
├── components/
│   └── TodoList.tsx        # 主组件
└── App.tsx                 # 应用入口

6. Zustand 与 useState 对比

✅ 使用 useState 的场景:

  • 管理表单输入、UI 开关状态等组件私有状态
  • 简单的父子组件通信(通过 props 传递)

✅ 使用 Zustand 的场景:

  • 多个无关组件需要共享状态(如全局登录 token、用户信息、主题设置)
  • 状态逻辑复杂,需要集中管理
  • 避免"props drilling"(层层传递 props)

结语:大道至简

Zustand 的哲学很简单 ------ "不要为了状态管理而引入复杂性"

它没有强制架构,没有繁琐规则,只提供最核心的能力:让状态管理变得简单、直观、高效

如果你正在寻找一种"刚刚好"的状态管理方案,Zustand 绝对值得尝试。它可能不会解决所有问题,但一定能让你:

  • ✅ 少写很多代码
  • ✅ 减少嵌套层级
  • ✅ 提升开发愉悦感

关注我,持续更新「从0死磕全栈」系列,带你一步步构建完整的全栈应用。

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax