代码世界是现实的镜像,状态管理教会我们:真正的控制不在于凝固不变,而在于优雅地引导变化。
这是「从0死磕全栈」系列的第5篇文章,前面我们已经完成了环境搭建、路由配置和基础功能开发。今天,我们将引入一个轻量级但强大的状态管理工具 ------ Zustand,来实现一个完整的 TodoList 应用。
Zustand 简介
Zustand 是一个轻量级、灵活的 React 状态管理库,以极简 API 解决复杂状态共享问题。
- ✅ 无需
Provider
包裹,直接导入使用 - ✅ 使用
create
创建 store,通过useStore
随时随地访问状态 - ✅ 支持中间件(如持久化、日志)、异步逻辑
- ✅ 代码简洁易维护,是中小型项目的理想选择
下面我们将用 React + TypeScript + Zustand 实现一个功能完整的 TodoList。
要实现的功能
-
添加待办事项
- 在输入框中输入内容,点击"添加"按钮或按 Enter 键
- 新事项添加到列表底部
-
标记完成/未完成
- 点击复选框切换完成状态
- 已完成事项显示删除线样式
-
删除事项
- 点击右侧"删除"按钮移除该事项
-
统计信息
- 显示总事项数和已完成事项数
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死磕全栈」系列,带你一步步构建完整的全栈应用。