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 就是那个高效、透明且严格执行财务制度的财务部。

相关推荐
NEXT062 小时前
别再折磨自己了!放弃 Redux 后,我用 Zustand + TS 爽到起飞
前端·react.js
2501_944711432 小时前
现代 React 路由实践指南
前端·react.js·前端框架
@PHARAOH3 小时前
WHAT - Vercel react-best-practices 系列(二)
前端·javascript·react.js
桃子叔叔3 小时前
react-wavesurfer录音组件1:从需求到组件一次说清楚
前端·react.js·前端框架
@PHARAOH3 小时前
WHAT - React startTransition vs setTimeout vs debounce
前端·react.js·前端框架
阿里巴巴终端技术3 小时前
二十年,重新出发!第 20 届 D2 技术大会「AI 新」议题全球征集正式开启
前端·react.js·html
Amumu121384 小时前
Redux介绍(二)
前端·react.js
Jinuss4 小时前
React16与React17+的JSX转换差异
前端·react.js
哈哈你是真的厉害5 小时前
React Native 鸿蒙跨平台开发:Badge 徽标
react native·react.js·harmonyos