DevToolBox免费
博客

Zustand 完全指南:React 轻量状态管理 (2026)

17 分钟阅读作者 DevToolBox Team
TL;DR

Zustand 是一个极简的 React 状态管理库,使用基于 hook 的 API,无需 Provider 或样板代码。通过 create() 创建 store,通过 hook 消费状态,通过选择器自动优化重渲染。内置 persist、devtools 和 immer 中间件,完美支持 TypeScript,优雅处理 Next.js SSR。压缩后不到 1KB,当 Redux 太重而 Context 重渲染太多时,Zustand 是最佳选择。

什么是 Zustand?为什么使用它?

Zustand(德语中意为"状态")是一个小巧、快速、可扩展的 React 状态管理库。由 Jotai 和 React Spring 背后的团队创建,Zustand 提供简单的基于 hook 的 API,无需 Provider、reducer 或 action creator。它将状态存储在 React 外部,更新只会触发实际使用了变更数据的组件重渲染。

npm install zustand
# or
yarn add zustand
# or
pnpm add zustand

Zustand vs Redux vs Jotai vs Recoil vs Context

每种状态管理方案都有优缺点。以下是 Zustand 与其他方案的对比。

特性ZustandRedux ToolkitJotaiRecoilReact Context
样板代码极少适中极少中等
需要 Provider不需要需要需要需要需要
包大小~1KB~11KB~3KB~14KB0KB(内置)
开发者工具中间件内置第三方扩展
TypeScript优秀良好优秀良好优秀
SSR 支持原生需配置原生有限原生

使用 create() 创建 Store

通过调用 create() 并传入接收 set 和 get 的函数来创建 Zustand store。返回值是一个 React hook,组件用它来访问 store。

基础 Store

最简单的 store 将状态和 actions 放在一起。set 函数默认合并部分状态,类似 React 的 setState。

import { create } from 'zustand';

// Create a counter store
const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Use in a component — no Provider needed!
function Counter() {
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

在 React 外部访问状态

Zustand store 可以在 React 组件外部访问。hook 本身有 getState() 和 setState() 方法用于命令式访问。

// Access state outside React
const currentCount = useCounterStore.getState().count;

// Update state imperatively
useCounterStore.setState({ count: 10 });

// Subscribe to all changes
const unsub = useCounterStore.subscribe((state) => {
  console.log('Count changed:', state.count);
});

// Unsubscribe when done
unsub();

选择器和防止重渲染

默认情况下,调用 useStore() 会订阅整个 store 并在每次变更时重渲染。使用选择器只选取组件需要的状态片段。

基础选择器

向 hook 传入选择器函数来提取特定状态。组件只在选中的值变化时重渲染(使用 Object.is 比较)。

const useTodoStore = create((set) => ({
  todos: [],
  filter: 'all',
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, done: false }],
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map((t) =>
      t.id === id ? { ...t, done: !t.done } : t
    ),
  })),
  setFilter: (filter) => set({ filter }),
}));

// Only re-renders when todos array changes
function TodoList() {
  const todos = useTodoStore((state) => state.todos);
  return todos.map((todo) => <div key={todo.id}>{todo.text}</div>);
}

// Only re-renders when filter changes
function FilterBar() {
  const filter = useTodoStore((state) => state.filter);
  const setFilter = useTodoStore((state) => state.setFilter);
  return <select value={filter} onChange={(e) => setFilter(e.target.value)} />;
}

对象选择器的浅比较

选择多个值时,使用 zustand/shallow 的 shallow 进行浅比较。这能防止对象结构相同时的不必要重渲染。

import { useShallow } from 'zustand/shallow';

// BAD: creates a new object every render -> infinite re-renders
// const { name, email } = useUserStore((s) => ({ name: s.name, email: s.email }));

// GOOD: useShallow compares each property individually
function UserProfile() {
  const { name, email } = useUserStore(
    useShallow((state) => ({ name: state.name, email: state.email }))
  );
  return <div>{name} ({email})</div>;
}

// Also works with arrays
function UserActions() {
  const [updateName, updateEmail] = useUserStore(
    useShallow((s) => [s.updateName, s.updateEmail])
  );
  // ...
}

Actions 和异步 Actions

Zustand 中的 actions 只是 store 内调用 set() 的函数。没有 dispatch,没有 action types,没有 reducers。异步 actions 同样简单。

同步 Actions

将 actions 和状态一起定义。set 函数接受部分状态对象或接收当前状态的更新函数。

const useCartStore = create((set, get) => ({
  items: [],
  totalPrice: 0,

  addItem: (product) => set((state) => {
    const existing = state.items.find((i) => i.id === product.id);
    if (existing) {
      return {
        items: state.items.map((i) =>
          i.id === product.id ? { ...i, qty: i.qty + 1 } : i
        ),
      };
    }
    return { items: [...state.items, { ...product, qty: 1 }] };
  }),

  removeItem: (id) => set((state) => ({
    items: state.items.filter((i) => i.id !== id),
  })),

  // Use get() to read current state inside an action
  calculateTotal: () => {
    const { items } = get();
    const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
    set({ totalPrice: total });
  },
}));

异步 Actions

异步 actions 就是普通的 async 函数。数据准备好后调用 set()。可以在 store 内跟踪 loading 和 error 状态。

const usePostStore = create((set) => ({
  posts: [],
  loading: false,
  error: null,

  fetchPosts: async () => {
    set({ loading: true, error: null });
    try {
      const res = await fetch('/api/posts');
      const posts = await res.json();
      set({ posts, loading: false });
    } catch (err) {
      set({ error: err.message, loading: false });
    }
  },

  createPost: async (data) => {
    const res = await fetch('/api/posts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    const newPost = await res.json();
    set((state) => ({ posts: [newPost, ...state.posts] }));
  },
}));

中间件:persist、devtools、immer、subscribeWithSelector

Zustand 支持包裹 store 创建器以添加功能的中间件。中间件通过嵌套进行组合。

persist — 自动本地存储

persist 中间件将状态保存到 localStorage(或任何存储)并在页面加载时恢复。可以配置哪些状态需要持久化。

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

const useSettingsStore = create(
  persist(
    (set) => ({
      theme: 'light',
      fontSize: 16,
      language: 'en',
      setTheme: (theme) => set({ theme }),
      setFontSize: (fontSize) => set({ fontSize }),
      setLanguage: (language) => set({ language }),
    }),
    {
      name: 'app-settings', // localStorage key
      storage: createJSONStorage(() => localStorage),
      // Only persist these fields
      partialize: (state) => ({
        theme: state.theme,
        fontSize: state.fontSize,
        language: state.language,
      }),
    }
  )
);

devtools — Redux DevTools 集成

devtools 中间件将 store 连接到 Redux DevTools 进行时间旅行调试和 action 检查。

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set(
        (state) => ({ count: state.count + 1 }),
        false,        // replace: false (merge)
        'increment'   // action name in DevTools
      ),
    }),
    { name: 'MyAppStore' } // DevTools instance name
  )
);

immer — 简化不可变更新

immer 中间件让你编写看起来可变的代码来产生不可变的更新。这对深层嵌套状态特别有用。

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

const useNestedStore = create(
  immer((set) => ({
    user: {
      profile: {
        name: 'Alice',
        address: { city: 'NYC', zip: '10001' },
      },
      settings: { notifications: true },
    },

    // Without immer you would need:
    // set((s) => ({ user: { ...s.user, profile: { ...s.user.profile,
    //   address: { ...s.user.profile.address, city: newCity } } } }))

    // With immer, just mutate the draft:
    updateCity: (city) => set((state) => {
      state.user.profile.address.city = city;
    }),

    toggleNotifications: () => set((state) => {
      state.user.settings.notifications =
        !state.user.settings.notifications;
    }),
  }))
);

// Combine middleware: immer + devtools + persist
const useAppStore = create(
  devtools(
    persist(
      immer((set) => ({
        // your state and actions
      })),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
);

subscribeWithSelector — 细粒度订阅

subscribeWithSelector 中间件支持在 React 外部订阅特定的状态片段。适用于副作用、日志记录或与外部系统同步。

import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';

const useStore = create(
  subscribeWithSelector((set) => ({
    count: 0,
    increment: () => set((s) => ({ count: s.count + 1 })),
  }))
);

// Subscribe to a specific slice
const unsub = useStore.subscribe(
  (state) => state.count,        // selector
  (count, prevCount) => {         // listener
    console.log('Count:', prevCount, '->', count);
    if (count >= 10) {
      console.log('Reached 10!');
    }
  },
  { fireImmediately: true }       // options
);

TypeScript 集成和类型化 Store

Zustand 提供一流的 TypeScript 支持。定义状态接口并作为泛型传递给 create()。

完全类型化的 Store

定义包含状态和 actions 的 State 接口。作为泛型参数传递给 create 以获得完整的类型安全。

interface AuthState {
  user: { id: string; name: string; email: string } | null;
  token: string | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  updateProfile: (data: Partial<{ name: string; email: string }>) => void;
}

const useAuthStore = create<AuthState>()((set) => ({
  user: null,
  token: null,
  isAuthenticated: false,

  login: async (email, password) => {
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const { user, token } = await res.json();
    set({ user, token, isAuthenticated: true });
  },

  logout: () => set({
    user: null, token: null, isAuthenticated: false,
  }),

  updateProfile: (data) => set((state) => ({
    user: state.user ? { ...state.user, ...data } : null,
  })),
}));

类型化选择器

选择器自动推断返回类型。也可以为复杂转换显式地给选择器函数定类型。

// Type is inferred automatically
function NavBar() {
  // userName: string | undefined (inferred)
  const userName = useAuthStore((s) => s.user?.name);
  // isAuth: boolean (inferred)
  const isAuth = useAuthStore((s) => s.isAuthenticated);

  return isAuth ? <span>Hello, {userName}</span> : <LoginButton />;
}

// Explicit selector type for complex derivations
const selectUserDisplay = (state: AuthState): string => {
  if (!state.user) return 'Guest';
  return state.user.name + ' (' + state.user.email + ')';
};

function UserBadge() {
  const display = useAuthStore(selectUserDisplay);
  return <span>{display}</span>;
}

大型 Store 的 Slices 模式

对于大型应用,将 store 拆分为 slices。每个 slice 管理状态的子集,可以通过完整 store 访问其他 slices。

创建和组合 Slices

每个 slice 是接收 set 和 get 并返回部分状态对象的函数。将 slices 组合到单个 create() 调用中。

// types.ts
interface UserSlice {
  user: { name: string } | null;
  setUser: (user: { name: string }) => void;
}

interface CartSlice {
  items: Array<{ id: string; name: string; qty: number }>;
  addItem: (item: { id: string; name: string }) => void;
  clearCart: () => void;
}

type AppState = UserSlice & CartSlice;

// slices/userSlice.ts
const createUserSlice = (set: any): UserSlice => ({
  user: null,
  setUser: (user) => set({ user }),
});

// slices/cartSlice.ts
const createCartSlice = (set: any, get: any): CartSlice => ({
  items: [],
  addItem: (item) => set((state: AppState) => ({
    items: [...state.items, { ...item, qty: 1 }],
  })),
  // Access user slice from cart slice via get()
  clearCart: () => {
    const user = get().user;
    console.log('Clearing cart for:', user?.name);
    set({ items: [] });
  },
});

// store.ts — combine slices
import { create } from 'zustand';

const useAppStore = create<AppState>()((...args) => ({
  ...createUserSlice(...args),
  ...createCartSlice(...args),
}));

在 Next.js 中使用 Zustand(SSR 水合)

Zustand 可以与 Next.js 配合使用,但需要对服务端渲染进行特殊处理。关键挑战是避免服务器上请求间共享状态。

SSR 安全的 Store 配置

创建一个 store 工厂,在服务器上为每个请求生成新的 store。使用 React context provider 将 store 实例传递给组件。

// store.ts
import { createStore } from 'zustand';

interface AppState {
  count: number;
  increment: () => void;
}

// Factory: creates a NEW store instance each call
export const createAppStore = (initialCount = 0) => {
  return createStore<AppState>()((set) => ({
    count: initialCount,
    increment: () => set((s) => ({ count: s.count + 1 })),
  }));
};

export type AppStore = ReturnType<typeof createAppStore>;

// provider.tsx
'use client';
import { createContext, useContext, useRef } from 'react';
import { useStore } from 'zustand';

const StoreContext = createContext<AppStore | null>(null);

export function StoreProvider({
  children,
  initialCount,
}: {
  children: React.ReactNode;
  initialCount: number;
}) {
  const storeRef = useRef<AppStore>(null);
  if (!storeRef.current) {
    storeRef.current = createAppStore(initialCount);
  }
  return (
    <StoreContext.Provider value={storeRef.current}>
      {children}
    </StoreContext.Provider>
  );
}

// Custom hook for accessing the store
export function useAppStore<T>(selector: (state: AppState) => T): T {
  const store = useContext(StoreContext);
  if (!store) throw new Error('Missing StoreProvider');
  return useStore(store, selector);
}

测试 Zustand Stores

Zustand stores 是普通的 JavaScript 对象,无需 React 渲染即可轻松测试。可以单独测试 stores 或与组件一起测试。

单元测试 Store

通过调用 actions 并直接检查状态来测试 store 逻辑。无需 React 渲染。

// counterStore.test.ts
import { useCounterStore } from './counterStore';

describe('counterStore', () => {
  // Reset store before each test
  beforeEach(() => {
    useCounterStore.setState({ count: 0 });
  });

  it('increments count', () => {
    useCounterStore.getState().increment();
    expect(useCounterStore.getState().count).toBe(1);
  });

  it('decrements count', () => {
    useCounterStore.setState({ count: 5 });
    useCounterStore.getState().decrement();
    expect(useCounterStore.getState().count).toBe(4);
  });

  it('resets count', () => {
    useCounterStore.setState({ count: 99 });
    useCounterStore.getState().reset();
    expect(useCounterStore.getState().count).toBe(0);
  });
});

测试使用 Store 的组件

测试使用 Zustand stores 的组件时,在每个测试前重置 store 以避免测试间状态泄漏。

// Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { useCounterStore } from './counterStore';
import { Counter } from './Counter';

// Reset store before each test
const initialState = useCounterStore.getState();
beforeEach(() => {
  useCounterStore.setState(initialState, true);
});

it('displays the count and increments on click', () => {
  render(<Counter />);
  expect(screen.getByText('0')).toBeInTheDocument();
  fireEvent.click(screen.getByText('+'));
  expect(screen.getByText('1')).toBeInTheDocument();
});

Zustand vs React Context

React Context 是内置且简单的,但有一个关键的性能问题:context 值的任何更新都会重渲染每个消费者。Zustand 通过将状态存储在 React 外部并使用选择器进行细粒度订阅来解决这个问题。

React Context 的问题

使用 Context 时,更改单个值会重渲染消费该 context 的每个组件,即使该组件不使用更改的值。Zustand 组件只在其选择的状态片段变化时重渲染。

// PROBLEM: React Context re-renders all consumers
const AppContext = React.createContext(null);

function App() {
  const [state, setState] = useState({
    theme: 'dark',
    user: 'Alice',
    count: 0,
  });
  return (
    <AppContext.Provider value={{ state, setState }}>
      {/* ALL children re-render when ANY value changes */}
      <ThemeDisplay />  {/* re-renders on count change */}
      <UserDisplay />   {/* re-renders on count change */}
      <Counter />       {/* re-renders on theme change */}
    </AppContext.Provider>
  );
}

// SOLUTION: Zustand with selectors
const useAppStore = create((set) => ({
  theme: 'dark',
  user: 'Alice',
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}));

// Only re-renders when theme changes
function ThemeDisplay() {
  const theme = useAppStore((s) => s.theme);
  return <div>Theme: {theme}</div>;
}

// Only re-renders when count changes
function Counter() {
  const count = useAppStore((s) => s.count);
  const increment = useAppStore((s) => s.increment);
  return <button onClick={increment}>{count}</button>;
}

性能优化

Zustand 默认就很快,但在大型应用中有一些模式可以榨取最大性能。

原子选择器

选择尽可能小的状态单元。当只需要一个属性时,避免选择整个对象。

// BAD: selects the entire user object
// Re-renders when ANY user property changes
function Avatar() {
  const user = useStore((s) => s.user);
  return <img src={user.avatar} />;
}

// GOOD: selects only what is needed
// Only re-renders when avatar URL changes
function Avatar() {
  const avatar = useStore((s) => s.user.avatar);
  return <img src={avatar} />;
}

瞬态更新(不触发重渲染)

对不需要触发重渲染的更新使用 subscribe(),如动画或频繁的数据流。

// Transient updates: update DOM directly without re-rendering
import { useEffect, useRef } from 'react';

const useMouseStore = create((set) => ({
  x: 0,
  y: 0,
  setPosition: (x, y) => set({ x, y }),
}));

// This component NEVER re-renders from mouse moves
function Cursor() {
  const ref = useRef(null);

  useEffect(() => {
    // Subscribe to store changes and update DOM directly
    const unsub = useMouseStore.subscribe((state) => {
      if (ref.current) {
        ref.current.style.transform =
          'translate(' + state.x + 'px, ' + state.y + 'px)';
      }
    });
    return unsub;
  }, []);

  return <div ref={ref} style={{ width: 20, height: 20 }} />;
}

最佳实践和常见模式

  • 保持 store 小而专注于单一领域 — 多个 store 优于一个巨型 store
  • 始终使用选择器防止不必要的重渲染 — 永远不要无参数调用 useStore()
  • 将 actions 和它们修改的状态放在同一个 store 中
  • 对深层嵌套状态更新使用 immer 中间件
  • 在测试设置中重置 stores 以防止测试间状态泄漏
  • 使用 persist 中间件的 partialize 只保存必要数据到存储
  • 选择多个值时优先使用 zustand/shallow 的 useShallow
  • 对于 SSR,为每个请求创建 store 实例并通过 context 传递

常见问题

Zustand 比 Redux 好吗?

Zustand 不是普遍更好,但对大多数应用是强有力的选择。Redux Toolkit 仍然适合受益于严格约定和中间件生态的大型团队。Zustand 在你需要最少样板代码、更小包体积和更简单心智模型时表现出色。

Zustand 需要 Provider 组件吗?

不需要。Zustand stores 默认存在于 React 树外部,不需要 Provider。唯一的例外是需要每个请求独立 store 实例的 SSR 场景。

Zustand 能替代 React Context 管理全局状态吗?

可以,而且推荐在大多数情况下使用。React Context 在任何值变化时重渲染所有消费者,而 Zustand 使用选择器只重渲染使用了变更数据的组件。

Zustand 如何处理 TypeScript?

Zustand 有优秀的 TypeScript 支持。定义 State 接口并作为泛型传给 create()。所有选择器、actions 和中间件都完全类型化。

Zustand 能将状态持久化到 localStorage 吗?

可以,使用内置的 persist 中间件。它自动保存状态到 localStorage 并在页面加载时恢复。可以自定义存储引擎和持久化的状态片段。

如何在 Next.js SSR 中使用 Zustand?

创建一个 store 工厂函数返回新的 store 实例。在服务器上,每个请求获得自己的 store 以防止用户间共享状态。通过 React context provider 传递 store。

Zustand 和 Jotai 有什么区别?

两者由同一团队创建。Zustand 使用自上而下的 store 方式,Jotai 使用自下而上的原子方式。当状态结构明确时选 Zustand,当偏好原子化、可组合的状态时选 Jotai。

Zustand 可以用于生产环境吗?

完全可以。Zustand 被数千家公司在生产中使用,在 GitHub 上有超过 50,000 颗星。它积极维护、文档完善且 API 稳定。

Key Takeaways
  • Zustand 提供最简单的 React 状态管理 API — 创建 store,使用 hook,完成
  • 选择器是性能的关键 — 始终只选择你需要的状态
  • 内置中间件(persist、devtools、immer)覆盖最常见需求,无需额外包
  • TypeScript 支持一流,stores、选择器和中间件都有完整类型推断
  • 压缩后不到 1KB,Zustand 对包体积几乎零开销
  • Slices 模式可扩展到大型应用同时保持代码组织有序
𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterTSJSON to TypeScript

相关文章

React 设计模式指南:复合组件、自定义 Hook、HOC、Render Props 与状态机

完整的 React 设计模式指南,涵盖复合组件、render props、自定义 hooks、高阶组件、Provider 模式、状态机、受控与非受控、组合模式、观察者模式、错误边界和模块模式。

React Query 模式 2026:TanStack Query 数据获取、缓存与变更

2026年精通React Query (TanStack Query) 模式:useQuery、useMutation、乐观更新与服务器状态管理。

Next.js 高级指南:App Router、服务端组件、数据获取、中间件与性能优化

完整的 Next.js 高级指南,涵盖 App Router 架构、React 服务端组件、流式 SSR、数据获取模式、中间件、路由处理器、并行和拦截路由、缓存策略、ISR、图片优化和部署最佳实践。