Skip to content
第 155 / 250 章前端⏱ 10 分钟阅读

第 155 章:状态管理 Zustand

学习目标

  • 理解 Zustand 的简洁 API
  • 掌握 store 创建与使用
  • 学会选择器(selector)优化
  • 替代 useContext 的轻量方案

一、Zustand 是什么

Zustand(德语"状态")是轻量级全局状态管理库,无需 Provider,API 简单。

二、安装

bash
pnpm add zustand

三、基础用法

3.1 创建 store

tsx
// stores/useUserStore.ts
import { create } from 'zustand';

interface User {
  id: number;
  name: string;
}

interface UserState {
  user: User | null;
  setUser: (user: User) => void;
  clear: () => void;
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  clear: () => set({ user: null }),
}));

3.2 在组件使用

tsx
'use client';
import { useUserStore } from '@/stores/useUserStore';

function UserInfo() {
  const user = useUserStore(s => s.user);
  const setUser = useUserStore(s => s.setUser);
  const clear = useUserStore(s => s.clear);

  if (!user) return <button onClick={() => setUser({ id: 1, name: 'Tom' })}>
    登录
  </button>;

  return (
    <div>
      <p>{user.name}</p>
      <button onClick={clear}>登出</button>
    </div>
  );
}

四、计数器示例

tsx
// stores/useCountStore.ts
import { create } from 'zustand';

interface CountState {
  count: number;
  inc: () => void;
  dec: () => void;
  reset: () => void;
}

export const useCountStore = create<CountState>((set) => ({
  count: 0,
  inc: () => set((s) => ({ count: s.count + 1 })),
  dec: () => set((s) => ({ count: s.count - 1 })),
  reset: () => set({ count: 0 }),
}));
tsx
// 组件
function Counter() {
  const count = useCountStore(s => s.count);
  const inc = useCountStore(s => s.inc);
  return <button onClick={inc}>{count}</button>;
}

五、持久化

bash
pnpm add zustand/middleware
tsx
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  token: string | null;
  setToken: (token: string) => void;
  clear: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      setToken: (token) => set({ token }),
      clear: () => set({ token: null }),
    }),
    { name: 'auth-storage' }   // localStorage key
  )
);

六、切片模式(大型应用)

tsx
// stores/useAppStore.ts
import { create } from 'zustand';
import { createUserSlice, UserSlice } from './userSlice';
import { createCartSlice, CartSlice } from './cartSlice';

type AppState = UserSlice & CartSlice;

export const useAppStore = create<AppState>()((...a) => ({
  ...createUserSlice(...a),
  ...createCartSlice(...a),
}));
tsx
// stores/userSlice.ts
import { StateCreator } from 'zustand';

export interface UserSlice {
  user: User | null;
  setUser: (user: User) => void;
}

export const createUserSlice: StateCreator<
  UserSlice, [], [], UserSlice
> = (set) => ({
  user: null,
  setUser: (user) => set({ user }),
});

七、性能优化:选择器

7.1 精确订阅

tsx
// ❌ 订阅整个 store,任何变化都重渲染
const state = useUserStore();

// ✅ 只订阅需要的字段
const user = useUserStore(s => s.user);

7.2 shallow 比较

tsx
import { shallow } from 'zustand/shallow';

const { user, setUser } = useUserStore(
  (s) => ({ user: s.user, setUser: s.setUser }),
  shallow
);

7.3 useShallow(v4+)

tsx
import { useShallow } from 'zustand/react/shallow';

const { user, setUser } = useUserStore(
  useShallow((s) => ({ user: s.user, setUser: s.setUser }))
);

八、组件外使用 store

tsx
// 不在 React 组件中访问 store
function logout() {
  const { clear } = useUserStore.getState();
  clear();
}

// 订阅变化
const unsub = useUserStore.subscribe((state) => {
  console.log('user 变了', state.user);
});

九、Zustand vs Redux vs Context

维度ZustandRedux ToolkitContext
包大小1KB10KB+0(内置)
模板代码极少
DevTools
持久化middleware手写手写
学习成本
适合中小型大型简单共享

十、Next.js 注意事项

Zustand 在 Next.js 14 中需要小心 SSR:

tsx
// stores/useUserStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

export const useUserStore = create<...>()(
  persist(
    (set) => ({ ... }),
    {
      name: 'user',
      storage: createJSONStorage(() => localStorage),
    }
  )
);
tsx
// 在组件中避免 SSR 不一致
'use client';
import { useEffect, useState } from 'react';
import { useUserStore } from '@/stores/useUserStore';

function UserInfo() {
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => setHydrated(true), []);
  const user = useUserStore(s => s.user);
  if (!hydrated) return null;
  return <div>{user?.name}</div>;
}

十一、本章小结

概念关键
create创建 store
选择器精确订阅,避免无关渲染
persistlocalStorage 持久化
切片拆分大型 store
中间件持久化、immer 等

动手练习

  1. 写一个购物车 store(增删改查、计算总价)
  2. 加 persist 持久化
  3. 用选择器优化一个多字段组件
  4. 组件外修改 store(token 刷新后清空)

推荐阅读


下一章:第 156 章:TypeScript 集成

本站基于 VitePress 构建 · 由 Codebook 团队维护