Zustand:若 React 组件是公民,谁来当“中央银行”?—— 打造轻量级企业级状态管理

前言 :在 React 的世界里,如果说组件(Component)是勤勤恳恳工作的公民,那么状态(State)就是流动的货币。当应用规模扩大,仅仅靠父子组件间的"现金交易"(Props drilling)已经无法维持经济系统的运转。我们需要一个中央银行,一个专业的财务管理部门。

今天,我们不谈繁琐的 Redux,而是聊聊 Zustand ------ 这个来自德国的"小而美"的状态管理库,看看它是如何通过极其精简的 API,帮我们把"企业做大做强"的。


一、 为什么我们需要"中央银行"?

我们在写 React 组件时,心中的公式往往是:

<math xmlns="http://www.w3.org/1998/Math/MathML"> U I = f ( S t a t e ) UI = f(State) </math>UI=f(State)

但在实际开发中,如果不引入全局状态管理,我们面临着几个痛点:

  1. 层级地狱:想要把孙子的状态传给爷爷,Props 需要传递数层。
  2. 兄弟失联:兄弟组件之间无法直接通信,状态必须提升(Lifting State Up)到共同父级,导致不必要的重渲染。

"企业做大做强,请管理财务、状态以及修改状态的规矩。"

Zustand 就是这样一个基于 Hooks 思想实现的中央管理系统。它将状态存入 Store(仓库),实现全局共享,且不需要在最外层包裹繁琐的 Provider。


二、 建立你的第一家"分行":基础状态管理

让我们从最简单的计数器开始。在 Zustand 中,创建一个 Store 就像开一家分店一样简单。

1. 定义规矩(Interface)与 存储(Store)

在"企业管理"中,不仅要有钱(Count),还要有动用这笔钱的规矩(Actions)。

TypeScript

typescript 复制代码
// store/counter.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

// 1. 定义账本的结构:即使是小钱,也要有类型约束
interface CounterState {
    count: number;
    increment: () => void;
    decrement: () => void;
    reset: () => void;
}

// 2. 创建金库,并制定修改规则
export const useCounterStore = create<CounterState>()(
    persist( // 使用中间件,相当于给金库加了把"永久保存"的锁
        (set) => ({
            // 列出资产(状态)
            count: 0,
            
            // 状态要怎么改?必须通过合法的手段(Action)
            // set 函数是 Zustand 的核心,它是唯一合法的修改器
            increment: () => set((state) => ({ count: state.count + 1 })),
            decrement: () => set((state) => ({ count: state.count - 1 })),
            reset: () => set({ count: 0 }),
        }),
        {
            name: 'counter-storage', // 存到 localStorage 里的名字
        }
    )
);

核心解读

  • create: 建立仓库。
  • set : 这是唯一的"财务审批笔"。你不能直接 state.count++,必须通过 set 返回一个新的对象。这保证了数据的不可变性(Immutability)
  • persist : 这是 Zustand 的杀手锏中间件。它自动将状态同步到 localStorage,刷新页面数据不丢失。

三、 处理复杂资产:对象与数组的不可变性

当我们的资产不仅仅是数字,而是复杂的待办事项列表(TodoList)或用户信息(User)时,不可变性的操作显得尤为重要。

1. Todo List 的增删改查

useTodoStore 中,我们看到了数组操作的标准范式:

TypeScript

typescript 复制代码
// store/todo.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { Todo } from '../types';

export interface TodoState {
    todos: Todo[];
    addTodo: (text: string) => void;
    toggleTodo: (id: number) => void;
    removeTodo: (id: number) => void;
}

export const useTodoStore = create<TodoState>()(
    persist(
        (set) => ({
            todos: [],
            // 新增:利用解构 [...old, new] 创建新数组
            addTodo: (text: string) =>
                set((state) => ({
                    todos: [...state.todos, {
                        id: Date.now(),
                        text,
                        completed: false
                    }]
                })),
            // 切换状态:利用 map 生成新数组,不修改原对象
            toggleTodo: (id: number) =>
                set((state) => ({
                    todos: state.todos.map((todo) =>
                        todo.id === id 
                            ? { ...todo, completed: !todo.completed } 
                            : todo
                    )
                })),
            // 删除:利用 filter 过滤
            removeTodo: (id: number) =>
                set((state) => ({
                    todos: state.todos.filter(todo => todo.id !== id)
                })),
        }),
        { name: 'todos-storage' }
    )
);

深度思考

这里的 mapfilter 和展开运算符 ... 不是为了炫技,而是为了配合 React 的更新机制。React 依赖引用的变化来感知更新,如果我们直接 todos.push(),引用不变,UI 就不会刷新。这就是"修改状态的规矩"

2. 用户鉴权状态

同样的逻辑适用于用户信息管理:

TypeScript

typescript 复制代码
// store/user.ts
interface UserState {
  isLoggin: boolean;
  user: User | null;
  login: (user: User) => void;
  logout: () => void;
}
// ... 代码省略,逻辑同上,利用 set 同时更新多个字段

四、 消费状态:在 UI 组件中提款

有了银行(Store),组件(App.tsx)就可以轻松地存取数据了。Zustand 的 Hook API 让这一切变得像使用 useState 一样自然。

TypeScript

javascript 复制代码
// App.tsx
import { useCounterStore } from './store/counter';
import { useTodoStore } from './store/todo';

function App() {
  // 1. 直接提取所需的 State 和 Actions
  // 就像从 ATM 取钱一样简单
  const { count, increment, decrement, reset } = useCounterStore();
  
  const { todos, addTodo, toggleTodo, removeTodo } = useTodoStore();
  
  // 2. 结合本地 UI 逻辑 (Input value)
  const [inputValue, setInputValue] = useState("");

  const handleAdd = () => {
    if (!inputValue.trim()) return;
    addTodo(inputValue); // 调用 Store 的 Action
    setInputValue("");
  }

  return (
    <div>
       {/* UI 渲染逻辑,完全解耦 */}
       <h1>Count: {count}</h1>
       <button onClick={increment}>+1</button>
       
       <ul>
         {todos.map(todo => (
           <li key={todo.id}>
             {/* 这里的 toggleTodo 直接来自 Store */}
             <span onClick={() => toggleTodo(todo.id)}>
               {todo.text}
             </span>
           </li>
         ))}
       </ul>
    </div>
  )
}

五、 总结:Zustand 的企业级管理哲学

回到开头提到的代码注释: "专业管理状态,修改状态的规矩"

Zustand 相比于其他工具,胜在平衡

  1. 极简主义 :没有 Boilerplate(样板代码),没有 Provider 包裹,即装即用。
  2. 规矩严明 :通过 TypeScript 接口定义 State,通过 Actions 封装修改逻辑。组件只负责"触发",Store 负责"执行"。
  3. 持久化persist 中间件让数据存储变得透明化。

如果把 React 应用比作一家公司,useState 是员工口袋里的零花钱,而 Zustand 就是那个高效、透明且严格执行财务制度的财务部。

相关推荐
pe7er9 小时前
状态提升:前端开发中的状态管理的设计思想
前端·vue.js·react.js
晚风予星11 小时前
Ant Design Token Lens 迎来了全面升级!支持在 .tsx 或 .ts 文件中直接使用 Design Token
前端·react.js·visual studio code
青青家的小灰灰15 小时前
React 架构进阶:自定义 Hooks 的高级设计模式与最佳实践
前端·react.js·前端框架
jonjia15 小时前
模块、脚本与声明文件
typescript
jonjia15 小时前
配置 TypeScript
typescript
jonjia15 小时前
TypeScript 工具函数开发
typescript
jonjia15 小时前
注解与断言
typescript
jonjia15 小时前
IDE 超能力
typescript
jonjia15 小时前
对象类型
typescript
jonjia15 小时前
快速搭建 TypeScript 开发环境
typescript