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

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

文章目录

  • [使用 Zustand 构建 Todo 应用完整教程](#使用 Zustand 构建 Todo 应用完整教程)
    • 项目简介
    • 技术栈
    • 项目结构
    • 核心代码解析
      • [1. Zustand Store 定义 (`src/store/todoStore.ts`)](#1. Zustand Store 定义 (src/store/todoStore.ts))
      • [2. 页面组件 (`app/page.tsx`)](#2. 页面组件 (app/page.tsx))
      • [3. 配置文件](#3. 配置文件)
        • [`package.json` 关键依赖](#package.json 关键依赖)
        • [`tsconfig.json` 路径别名](#tsconfig.json 路径别名)
    • 复现步骤
      • [步骤 1:创建 Next.js 项目](#步骤 1:创建 Next.js 项目)
      • [步骤 2:安装依赖](#步骤 2:安装依赖)
      • [步骤 3:创建 Store 文件](#步骤 3:创建 Store 文件)
      • [步骤 4:创建页面组件](#步骤 4:创建页面组件)
      • [步骤 5:运行项目](#步骤 5:运行项目)
    • 功能演示
    • 核心概念总结
    • 扩展建议
    • 总结

使用 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)

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)

ts 复制代码
// 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 关键依赖
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 路径别名
json 复制代码
    "paths": {
      "@/*": ["./*"]
    }

复现步骤

步骤 1:创建 Next.js 项目

bash 复制代码
npx create-next-app@latest my-todo-app
# 选择 TypeScript、Tailwind CSS、App Router

步骤 2:安装依赖

bash 复制代码
npm install zustand

步骤 3:创建 Store 文件

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

步骤 4:创建页面组件

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

步骤 5:运行项目

bash 复制代码
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 和状态管理的入门项目。

相关推荐
阿珊和她的猫2 小时前
WebRTC 技术深度解析:实时通信的未来引擎
前端·webpack·node.js·webrtc
silence_xiang2 小时前
【React】首页悬浮球实现,点击出现悬浮框
前端·javascript·react.js
申阳2 小时前
Day 11:集成百度统计以监控站点流量
前端·后端·程序员
Cache技术分享2 小时前
239. Java 集合 - 通过 Set、SortedSet 和 NavigableSet 扩展 Collection 接口
前端·后端
超级罗伯特2 小时前
大屏自适应,响应式布局,亲测有效
前端·javascript·html·大屏·驾驶舱
青衫码上行2 小时前
【Java Web学习 | 第九篇】JavaScript(3) 数组+函数
java·开发语言·前端·javascript·学习
前端老宋Running2 小时前
React组件命名为什么用小写开头会无法运行?
前端·react.js·面试
百***07182 小时前
WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程
前端
ruanCat2 小时前
对 changelogen 和 changelogithub 使用的思考
前端·github