Zustand + TypeScript:前端“中央银行”是如何炼成的?

5 阅读4分钟

当你的 React 项目开始“通货膨胀”,是时候请出 Zustand 这位央行行长了!


💡 引子:为什么前端也需要“中央银行”?

想象一下,你正在开发一个 Todo 应用。一开始,状态少得可怜——一个 count、一个 todos 列表,组件之间传传 props 就搞定了。

但随着业务扩张,你的应用开始“经济过热”:

  • 用户登录状态要全局共享
  • 购物车数据要在多个页面同步
  • 深色/浅色主题切换要实时生效
  • 网络请求的 loading 状态要统一管理

很快,你的组件树变成了“状态走私网络”:props 一层层往下传,回调函数一层层往上冒,代码像意大利面条一样缠在一起。

这时候,你就需要一个 中央状态管理系统 —— 就像国家需要中央银行来统一发行货币、调控经济一样。

而在 React 的世界里,Zustand 就是那位低调、高效、不搞复杂仪式感的“央行行长”。


🐻 为什么是 Zustand?而不是 Redux?

Redux 很强大,但它有点像“美联储”——规则多、流程繁、仪式感强:

  • 必须写 action、reducer、store
  • middleware 配置像填税务申报表
  • TypeScript 支持虽好,但类型推导常让人头秃

Zustand 呢?它更像一个“极简主义央行”:

✅ 基于 Hooks,天然拥抱 React 函数式思维
✅ 一行代码创建 store,无需样板代码
✅ 内置 TypeScript 支持,类型推导丝滑如德芙
✅ 自带 persist 中间件,状态持久化只需两行
✅ 轻量(< 1KB gzipped),性能优异

“Zustand” 在德语中意为 “状态” —— 它不玩花哨,只专注一件事:管理状态


🛠️ 实战:用 TypeScript + Zustand 打造“三权分立”状态体系

我们以一个典型的前端项目为例,拆解三个核心状态模块:

1️⃣ 计数器 Store:最简单的“货币政策”

// store/counter.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

export const useCounterStore = create<CounterState>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
      reset: () => set({ count: 0 }),
    }),
    { name: 'counter' } // 自动存入 localStorage
  )
);

💡 persist 是 Zustand 官方中间件,自动帮你把状态存到 localStorage,刷新不丢失!比手写 useEffect + JSON.parse 高到不知道哪里去了。


2️⃣ Todo Store:带业务逻辑的“财政系统”

// store/todo.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { Todo } from '../types';

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

export const useTodoStore = create<TodoState>()(
  persist(
    (set) => ({
      todos: [],
      addTodo: (text) =>
        set((state) => ({
          todos: [
            ...state.todos,
            { id: Date.now(), text, completed: false }
          ]
        })),
      toggleTodo: (id) =>
        set((state) => ({
          todos: state.todos.map(todo =>
            todo.id === id ? { ...todo, completed: !todo.completed } : todo
          )
        })),
      removeTodo: (id) =>
        set((state) => ({
          todos: state.todos.filter(todo => todo.id !== id)
        }))
    }),
    { name: 'todos' }
  )
);

✨ 注意:这里 id: Date.now() 虽然简单,但在高并发场景下可能重复。生产环境建议用 crypto.randomUUID()nanoid


3️⃣ 用户 Store:安全敏感的“央行金库”

// store/user.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { User } from '../types';

interface UserState {
  isLoggedIn: boolean;
  user: User | null;
  login: (credentials: { username: string; password: string }) => void;
  logout: () => void;
}

export const useUserStore = create<UserState>()(
  persist(
    (set) => ({
      isLoggedIn: false,
      user: null,
      login: ({ username, password }) => {
        // TODO: 调用 API 验证
        if (password === '123456') {
          set({
            isLoggedIn: true,
            user: { id: 1, username, avatar: '/avatar.jpg' }
          });
        }
      },
      logout: () => set({ isLoggedIn: false, user: null })
    }),
    { 
      name: 'user',
      // 可选:自定义序列化,避免存入敏感信息
      // serialize: (state) => JSON.stringify({ ...state, password: undefined })
    }
  )
);

🔒 安全提示:永远不要在客户端存储密码persist 默认会序列化整个 state,记得过滤敏感字段。


🎯 在组件中使用:像呼吸一样自然

// App.tsx
import { useCounterStore } from './store/counter';
import { useTodoStore } from './store/todo';

function App() {
  const { count, increment, decrement } = useCounterStore();
  const { todos, addTodo } = useTodoStore();

  return (
    <div>
      <button onClick={increment}>Count: {count}</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

🌟 Zustand 的魔法:每个 useStore() 只订阅它用到的状态!
即使 todos 变化,count 组件也不会 re-render —— 自动优化,无需 memo


🤔 思考:Zustand 是银弹吗?

当然不是。但它解决了 90% 的状态管理问题,而且学习成本极低

场景推荐方案
简单全局状态(主题、用户)✅ Zustand
复杂数据流(金融交易系统)⚠️ 考虑 Redux + RTK
服务端状态(API 数据)🔄 React Query / SWR
跨应用通信🌐 BroadcastChannel / Custom Events

Zustand 不是取代 Redux,而是让大多数项目不再需要 Redux


🎁 彩蛋:Zustand 的隐藏技能

1. 组合 Store

const useCombinedStore = () => ({
  ...useCounterStore(),
  ...useTodoStore()
});

2. 异步 Action

fetchTodos: async () => {
  const todos = await api.getTodos();
  set({ todos });
}

3. DevTools 支持

import { devtools } from 'zustand/middleware';

create(devtools(...))

→ 自动接入 Redux DevTools,时间旅行调试!


✅ 结语:少即是多,简单即强大

Zustand 的哲学很简单:

“不要为了管理状态,而让代码变得难以管理。”

它没有复杂的概念,没有强制的模式,只有:

  • 一个 create
  • 一个 set
  • 一堆 TypeScript 类型

却能让你的前端项目状态清晰、逻辑集中、扩展无忧

所以,下次当你的 React 项目开始“状态通胀”时,别急着引入重型框架——
先问问自己:我是不是只需要一位像 Zustand 这样高效的“央行行长”?