Zustand 状态管理教程

学习使用 Zustand 进行简洁高效的 React 状态管理,掌握现代状态管理的最佳实践

为什么选择 Zustand?

Zustand 是一个轻量级的 React 状态管理库,提供了简洁的 API 和强大的功能。 相比 Redux,它有更少的样板代码;相比 Context API,它有更好的性能。

简洁 API

最少的样板代码,直观易用

高性能

选择性订阅,避免不必要的重渲染

灵活扩展

丰富的中间件生态系统

基础 Store 创建

Zustand 的核心是 create 函数,它接受一个函数并返回一个 hook。 这个函数接收 set 和 get 参数来管理状态。

基础 Store 示例
// stores/useCounterStore.ts
import { create } from 'zustand';

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

export const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// 在组件中使用
function Counter() {
  const { count, increment, decrement, reset } = useCounterStore();

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>重置</button>
    </div>
  );
}

简单状态管理演示

体验 Zustand 管理基本状态,包括计数器和待办事项列表

简单计数器状态

0

待办事项列表状态

学习 Zustand
构建状态管理
总共 2 项,已完成 1

高级 Store 设计

高级 Store 可以包含复杂的状态结构、计算属性、中间件支持等。 通过合理的设计,可以构建出功能强大且易于维护的状态管理系统。

高级 Store 设计
// stores/useAppStore.ts
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { persist, createJSONStorage } from 'zustand/middleware';

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

interface Todo {
  id: number;
  text: string;
  completed: boolean;
  createdAt: Date;
}

interface AppState {
  // 用户状态
  user: User | null;
  setUser: (user: User | null) => void;

  // 待办事项状态
  todos: Todo[];
  addTodo: (text: string) => void;
  toggleTodo: (id: number) => void;
  deleteTodo: (id: number) => void;
  clearCompleted: () => void;

  // UI 状态
  isLoading: boolean;
  setLoading: (loading: boolean) => void;

  // 主题状态
  theme: 'light' | 'dark';
  toggleTheme: () => void;

  // 计算属性
  completedTodos: Todo[];
  pendingTodos: Todo[];
  todoStats: {
    total: number;
    completed: number;
    pending: number;
  };
}

export const useAppStore = create<AppState>()(
  subscribeWithSelector(
    persist(
      (set, get) => ({
        // 初始状态
        user: null,
        todos: [],
        isLoading: false,
        theme: 'light',

        // 用户相关操作
        setUser: (user) => set({ user }),

        // 待办事项操作
        addTodo: (text) => set((state) => ({
          todos: [
            ...state.todos,
            {
              id: Date.now(),
              text,
              completed: false,
              createdAt: new Date(),
            },
          ],
        })),

        toggleTodo: (id) => set((state) => ({
          todos: state.todos.map(todo =>
            todo.id === id ? { ...todo, completed: !todo.completed } : todo
          ),
        })),

        deleteTodo: (id) => set((state) => ({
          todos: state.todos.filter(todo => todo.id !== id),
        })),

        clearCompleted: () => set((state) => ({
          todos: state.todos.filter(todo => !todo.completed),
        })),

        // UI 状态操作
        setLoading: (isLoading) => set({ isLoading }),

        // 主题操作
        toggleTheme: () => set((state) => ({
          theme: state.theme === 'light' ? 'dark' : 'light',
        })),

        // 计算属性(getters)
        get completedTodos() {
          return get().todos.filter(todo => todo.completed);
        },

        get pendingTodos() {
          return get().todos.filter(todo => !todo.completed);
        },

        get todoStats() {
          const todos = get().todos;
          return {
            total: todos.length,
            completed: todos.filter(t => t.completed).length,
            pending: todos.filter(t => !t.completed).length,
          };
        },
      }),
      {
        name: 'app-store', // 本地存储的键名
        storage: createJSONStorage(() => localStorage),
        // 只持久化特定字段
        partialize: (state) => ({
          user: state.user,
          todos: state.todos,
          theme: state.theme,
        }),
      }
    )
  )
);

复杂状态管理演示

展示 Zustand 处理复杂应用状态,包括用户、购物车、通知等多个状态模块

用户状态

张三

张三

zhangsan@example.com

购物车状态 (3 件商品)

MacBook Pro

¥12,999

1

iPhone 15

¥5,999

2
总计:¥24,997

通知状态 (2)

欢迎使用 Zustand!

10:30:00

购物车已更新

10:25:00

中间件使用

Zustand 提供了丰富的中间件来扩展功能,包括持久化、订阅、 日志记录、Redux DevTools 集成等。

中间件使用示例
// 中间件使用示例

// 1. 持久化中间件
import { persist, createJSONStorage } from 'zustand/middleware';

const usePersistentStore = create(
  persist(
    (set) => ({
      data: null,
      setData: (data) => set({ data }),
    }),
    {
      name: 'persistent-store',
      storage: createJSONStorage(() => localStorage),
      // 自定义序列化
      serialize: (state) => JSON.stringify(state),
      deserialize: (str) => JSON.parse(str),
      // 版本控制和迁移
      version: 1,
      migrate: (persistedState, version) => {
        if (version === 0) {
          // 从版本 0 迁移到版本 1
          return { ...persistedState, newField: 'default' };
        }
        return persistedState;
      },
    }
  )
);

// 2. 订阅中间件
import { subscribeWithSelector } from 'zustand/middleware';

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

// 订阅特定字段变化
useSubscriptionStore.subscribe(
  (state) => state.count,
  (count, prevCount) => {
    console.log('计数从', prevCount, '变为', count);
  }
);

// 3. 日志中间件
const logger = (config) => (set, get, api) =>
  config(
    (...args) => {
      console.log('State before:', get());
      set(...args);
      console.log('State after:', get());
    },
    get,
    api
  );

const useLoggedStore = create(
  logger((set) => ({
    data: null,
    setData: (data) => set({ data }),
  }))
);

// 4. Redux DevTools 中间件
import { devtools } from 'zustand/middleware';

const useDevtoolsStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }), undefined, 'increment'),
      decrement: () => set((state) => ({ count: state.count - 1 }), undefined, 'decrement'),
    }),
    {
      name: 'counter-store',
    }
  )
);

在组件中使用

在 React 组件中使用 Zustand Store 非常简单,支持选择性订阅、 异步操作等高级用法。

组件中使用 Zustand
// 在组件中使用 Zustand

// 1. 基本使用
function TodoList() {
  const { todos, addTodo, toggleTodo, deleteTodo } = useAppStore();
  const [newTodo, setNewTodo] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (newTodo.trim()) {
      addTodo(newTodo);
      setNewTodo('');
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="添加待办事项"
        />
        <button type="submit">添加</button>
      </form>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
            <button onClick={() => deleteTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// 2. 选择性订阅(避免不必要的重渲染)
function TodoStats() {
  // 只订阅 todoStats,其他状态变化不会触发重渲染
  const stats = useAppStore((state) => state.todoStats);

  return (
    <div>
      <p>总计: {stats.total}</p>
      <p>已完成: {stats.completed}</p>
      <p>待完成: {stats.pending}</p>
    </div>
  );
}

// 3. 使用 shallow 比较
import { shallow } from 'zustand/shallow';

function UserProfile() {
  const { user, setUser } = useAppStore(
    (state) => ({ user: state.user, setUser: state.setUser }),
    shallow
  );

  if (!user) return <div>请登录</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// 4. 异步操作
function DataFetcher() {
  const { setLoading, setUser } = useAppStore();

  const fetchUser = async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/user');
      const user = await response.json();
      setUser(user);
    } catch (error) {
      console.error('获取用户失败:', error);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchUser();
  }, []);

  const isLoading = useAppStore((state) => state.isLoading);

  if (isLoading) return <div>加载中...</div>;

  return <UserProfile />;
}

最佳实践

遵循最佳实践可以让你的 Zustand 应用更加健壮、可维护和高性能。

Zustand 最佳实践
// Zustand 最佳实践

// 1. 按功能分割 Store
// stores/useAuthStore.ts
export const useAuthStore = create<AuthState>((set, get) => ({
  user: null,
  token: null,
  login: async (credentials) => {
    const response = await authAPI.login(credentials);
    set({ user: response.user, token: response.token });
  },
  logout: () => set({ user: null, token: null }),
}));

// stores/useCartStore.ts
export const useCartStore = create<CartState>((set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({
    items: [...state.items, item]
  })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)
  })),
}));

// 2. 使用 Immer 处理复杂状态
import { produce } from 'immer';

const useComplexStore = create((set) => ({
  nested: {
    deeply: {
      nested: {
        value: 0
      }
    }
  },
  updateNested: (newValue) => set(produce((state) => {
    state.nested.deeply.nested.value = newValue;
  })),
}));

// 3. 类型安全的 Slice 模式
interface UserSlice {
  user: User | null;
  setUser: (user: User | null) => void;
}

interface TodoSlice {
  todos: Todo[];
  addTodo: (text: string) => void;
}

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

const createTodoSlice: StateCreator<UserSlice & TodoSlice, [], [], TodoSlice> = (set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, completed: false }],
  })),
});

export const useAppStore = create<UserSlice & TodoSlice>()((...a) => ({
  ...createUserSlice(...a),
  ...createTodoSlice(...a),
}));

// 4. Store 重置功能
interface StoreState {
  data: any[];
  user: User | null;
  reset: () => void;
}

const initialState = {
  data: [],
  user: null,
};

export const useResetableStore = create<StoreState>((set) => ({
  ...initialState,
  reset: () => set(initialState),
}));

// 5. 外部 Store 访问
// 在组件外部访问 store
export const getStoreValue = () => useAppStore.getState();
export const subscribeToStore = (listener) => useAppStore.subscribe(listener);

// 在非 React 环境中使用
async function externalFunction() {
  const { user } = useAppStore.getState();
  if (user) {
    // 执行需要用户登录的操作
    await performUserAction();
  }
}

Zustand 练习场

尝试创建自己的 Zustand Store,体验状态管理的强大功能:

Zustand 状态管理练习

加载编辑器中...

Zustand vs 其他状态管理

Zustand

  • • 极简 API,学习成本低
  • • 无需 Provider 包装
  • • 支持选择性订阅
  • • TypeScript 友好
  • • 轻量级 (2.9kb)

Redux Toolkit

  • • 强大的生态系统
  • • 时间旅行调试
  • • 严格的不可变性
  • • 复杂的样板代码
  • • 较大的包体积

Context API

  • • React 内置
  • • 适合简单状态
  • • 需要多个 Provider
  • • 性能问题
  • • 复杂状态难以管理

恭喜你完成了所有教程!

通过这个完整的 Next.js 15 教程系列,你已经掌握了现代前端开发的核心技能。 从 Next.js 基础到 TypeScript、数据库操作、缓存、文件存储和状态管理, 你现在可以构建功能完整的现代 Web 应用了。