代码改变世界

React zustand todos案例(带本地存储localStorage、persist)todoStore.ts - 详解

2025-12-11 20:22  tlnshuju  阅读(0)  评论(0)    收藏  举报

参考文章:React zustand教程(create函数、persist中间件、zustand/middleware、Redux DevTools)查看本地存储localStorage、immer

使用 Zustand 构建 Todo 应用完整教程

项目简介

基于 Next.js 15 和 Zustand 的 Todo 应用,支持:

  • 添加任务
  • 切换完成状态
  • 批量选择/取消选择
  • 删除选中任务
  • 清除已完成的选中任务
  • 数据持久化(localStorage)

在这里插入图片描述

技术栈

  • Next.js 15.5.4
  • React 19.1.0
  • Zustand 5.0.8(状态管理)
  • TypeScript 5
  • Tailwind CSS 4.1.14

项目结构

my-project/
├── app/
│   ├── page.tsx          # 主页面组件
│   ├── layout.tsx        # 根布局
│   └── globals.css       # 全局样式
├── src/
│   └── store/
│       └── todoStore.ts  # Zustand 状态管理
├── package.json
└── tsconfig.json

核心代码解析

1. Zustand Store 定义 (src/store/todoStore.ts)

// src/store/todoStore.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
type Todo = { id: string; text: string; done: boolean; selected?: boolean }
interface TodoState {
todos: Todo[]
add: (text: string) => void
toggle: (id: string) => void
toggleSelect: (id: string) => void
selectAll: () => void
deselectAll: () => void
remove: (id?: string) => void
clearDone: () => void
}
export const useTodoStore = create<TodoState>()(
  persist(
  (set) => ({
  todos: [],
  add: (text) =>
  set((state) => ({
  todos: [...state.todos, { id: Date.now().toString(), text, done: false, selected: false }],
  })),
  // 切换完成状态,如果id相同,则done状态取反,否则不变
  toggle: (id) =>
  set((state) => ({
  todos: state.todos.map(t => t.id === id ? { ...t, done: !t.done } : t),
  })),
  // 切换选中状态
  toggleSelect: (id) =>
  set((state) => ({
  todos: state.todos.map(t => t.id === id ? { ...t, selected: !t.selected } : t),
  })),
  // 全选
  selectAll: () =>
  set((state) => ({
  todos: state.todos.map(t => ({ ...t, selected: true })),
  })),
  // 取消全选
  deselectAll: () =>
  set((state) => ({
  todos: state.todos.map(t => ({ ...t, selected: false })),
  })),
  // 删除:如果传入id则删除该id,否则删除所有选中的项
  remove: (id) => {
  if (id) {
  set((state) => ({ todos: state.todos.filter(t => t.id !== id) }));
  } else {
  set((state) => ({ todos: state.todos.filter(t => !t.selected) }));
  }
  },
  // 清除已完成:删除所有选中的已完成项
  clearDone: () => set((state) => ({
  todos: state.todos.filter(t => !(t.selected && t.done))
  })),
  }),
  { name: 'todo-storage' }
  )
  )

代码解析:

  • create:创建 Zustand store
  • persist:持久化中间件,数据保存到 localStorage(key: todo-storage
  • Todo:任务类型,包含 idtextdoneselected
  • TodoState:store 接口,包含状态和方法
  • set:更新状态,使用函数式更新保证不可变

方法说明:

  • add:添加任务,使用时间戳作为 id
  • toggle:切换完成状态
  • toggleSelect:切换选中状态
  • selectAll/deselectAll:批量选择/取消
  • remove:删除单个(传 id)或批量删除选中项(不传 id)
  • clearDone:删除已完成的选中项

2. 页面组件 (app/page.tsx)

// page.tsx
'use client'
import { useTodoStore } from '@/src/store/todoStore'
export default function ProfilePage() {
const { todos, add, toggle, remove, clearDone, selectAll, deselectAll, toggleSelect } = useTodoStore()
return (
<div className="p-8">
  <div className="flex gap-2">
    <button onClick={() => add('新任务')}>添加</button>
      <button onClick={() => selectAll()}>全选</button>
        <button onClick={() => deselectAll()}>取消全选</button>
          <button onClick={() => remove()}>删除</button>
            <button onClick={clearDone}>清除已完成</button>
              </div>
                <ul>
                  {todos.map(todo => (
                  <li key={todo.id} className="flex gap-2">
                    <span>id: {todo.id}</span>
                      <span>text: {todo.text}</span>
                        <span>done: {todo.done ? '已完成' : '未完成'}</span>
                          <input type="checkbox" checked={todo.done} onChange={() => toggle(todo.id)} />
                            <span>{todo.selected ? '选中' : '未选中'}</span>
                              <input type="checkbox" checked={todo.selected} onChange={() => toggleSelect(todo.id)} />
                                </li>
                                  ))}
                                  </ul>
                                    </div>
                                      )
                                      }

代码解析:

  • 'use client':Next.js 客户端组件
  • useTodoStore():获取 store 状态和方法
  • 按钮:触发对应操作
  • 列表:渲染任务,每个任务显示 id、文本、完成状态、完成复选框、选中状态、选中复选框

3. 配置文件

package.json 关键依赖
"dependencies": {
"@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/postcss": "^4.1.14",
"@tanstack/react-query": "^5.90.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.545.0",
"next": "15.5.4",
"postcss": "^8.5.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-error-boundary": "^6.0.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.9.4",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.14",
"zustand": "^5.0.8"
},
tsconfig.json 路径别名
"paths": {
"@/*": ["./*"]
}

复现步骤

步骤 1:创建 Next.js 项目

npx create-next-app@latest my-todo-app
# 选择 TypeScript、Tailwind CSS、App Router

步骤 2:安装依赖

npm install zustand

步骤 3:创建 Store 文件

创建 src/store/todoStore.ts,复制上面的 store 代码。

步骤 4:创建页面组件

修改 app/page.tsx,复制上面的页面组件代码。

步骤 5:运行项目

npm run dev

访问 http://localhost:3000 查看效果。

功能演示

  1. 添加任务:点击“添加”按钮,添加一个名为“新任务”的任务
  2. 切换完成状态:点击任务的完成复选框
  3. 选择任务:点击任务的选中复选框
  4. 全选/取消全选:使用顶部按钮
  5. 删除选中:点击“删除”按钮,删除所有选中的任务
  6. 清除已完成:点击“清除已完成”按钮,删除所有已完成的选中任务
  7. 数据持久化:刷新页面后数据仍然保留(存储在 localStorage)

核心概念总结

  1. Zustand:轻量级状态管理,API 简单
  2. 持久化:使用 persist 中间件自动保存到 localStorage
  3. 不可变更新:使用 set 函数式更新,保持状态不可变
  4. TypeScript:类型安全,提供良好的开发体验
  5. 函数式编程:使用 mapfilter 等函数式方法处理数组

扩展建议

  1. 添加输入框:让用户输入自定义任务文本
  2. 编辑功能:支持修改任务内容
  3. 分类功能:为任务添加分类标签
  4. 优先级:添加优先级设置
  5. 日期提醒:添加截止日期功能
  6. UI 优化:使用更美观的 UI 组件库(如 shadcn/ui)

总结

该示例展示了:

  • Zustand 的基本用法
  • 状态持久化
  • TypeScript 类型定义
  • Next.js 客户端组件
  • 函数式状态更新

适合作为学习 Zustand 和状态管理的入门项目。