Zustand 教程

1. 项目概述

Zustand 是一个轻量级的状态管理库,基于简化的 Flux 架构,它提供了一种简单、直观的方式来管理 React 应用的状态。与 Redux 相比,Zustand 更加轻量、简单,并且不需要繁琐的配置。

1.1 主要特性

  • 轻量级:核心代码只有几百行,无依赖
  • 简单易用:API 简洁明了,学习曲线平缓
  • 无需 Provider:不需要在应用顶层包裹 Provider
  • 中间件支持:内置持久化、devtools 等中间件
  • 订阅机制:可以选择性地订阅状态的一部分
  • TypeScript 支持:内置类型定义
  • 性能优异:避免不必要的渲染
  • 与 React 生态系统集成:支持 React 18 的并发特性
  • 可用于非 React 环境:核心逻辑不依赖 React

1.2 适用场景

  • 中小型 React 应用
  • 需要简单状态管理的项目
  • 对性能有要求的应用
  • 不希望使用复杂状态管理库的项目
  • 需要与其他状态管理库共存的项目

2. 安装与设置

2.1 环境要求

  • Node.js 14.0 或更高版本
  • npm 或 yarn 包管理器
  • React 16.8 或更高版本(支持 Hooks)

2.2 安装步骤

# 使用 npm 安装
npm install zustand

# 使用 yarn 安装
yarn add zustand

# 使用 pnpm 安装
pnpm add zustand

2.3 基本项目结构

my-zustand-app/
├── src/
│   ├── store/
│   │   ├── counterStore.js       # 计数器状态
│   │   └── userStore.js          # 用户状态
│   ├── components/
│   │   ├── Counter.js            # 计数器组件
│   │   └── UserProfile.js        # 用户信息组件
│   ├── App.js                    # 根组件
│   └── index.js                  # 应用入口
├── package.json                  # 项目配置
└── package-lock.json             # 依赖锁定

3. 核心概念

3.1 Store

Store 是 Zustand 的核心概念,它是一个包含状态和修改状态方法的对象。

// src/store/counterStore.js
import create from 'zustand';

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

export default useCounterStore;

3.2 Set Function

set 函数是创建 store 时传入的参数,它用于修改状态。set 函数可以接收一个对象或一个函数:

  • 对象形式:直接替换状态中的对应属性
  • 函数形式:接收当前状态,返回新的状态对象

3.3 Get Function

get 函数用于获取当前状态,它可以在 action 中使用:

const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  getDoubleCount: () => {
    const currentCount = get().count;
    return currentCount * 2;
  },
}));

3.4 订阅

Zustand 提供了订阅机制,允许组件只订阅状态的一部分,从而避免不必要的渲染:

// 订阅整个状态
const count = useCounterStore((state) => state.count);

// 订阅多个状态
const { count, increment } = useCounterStore((state) => ({
  count: state.count,
  increment: state.increment,
}));

3.5 中间件

Zustand 支持中间件,用于扩展 store 的功能:

import { persist, devtools } from 'zustand/middleware';

const useCounterStore = create(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      {
        name: 'counter-storage', // 存储键名
      }
    )
  )
);

4. 基本使用

4.1 创建 Store

基本 store

// src/store/counterStore.js
import { create } from 'zustand';

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

export default useCounterStore;

带 get 函数的 store

// src/store/userStore.js
import { create } from 'zustand';

const useUserStore = create((set, get) => ({
  users: [],
  loading: false,
  error: null,
  addUser: (user) => set((state) => ({
    users: [...state.users, user],
  })),
  removeUser: (userId) => set((state) => ({
    users: state.users.filter((user) => user.id !== userId),
  })),
  updateUser: (userId, updates) => set((state) => ({
    users: state.users.map((user) =>
      user.id === userId ? { ...user, ...updates } : user
    ),
  })),
  getUserById: (userId) => {
    const { users } = get();
    return users.find((user) => user.id === userId);
  },
}));

export default useUserStore;

4.2 在组件中使用

基本用法

// src/components/Counter.js
import React from 'react';
import useCounterStore from '../store/counterStore';

function Counter() {
  // 订阅状态和方法
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);
  const reset = useCounterStore((state) => state.reset);

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default Counter;

解构用法

// src/components/UserList.js
import React from 'react';
import useUserStore from '../store/userStore';

function UserList() {
  // 一次性订阅多个状态和方法
  const { users, addUser, removeUser } = useUserStore((state) => ({
    users: state.users,
    addUser: state.addUser,
    removeUser: state.removeUser,
  }));

  const handleAddUser = () => {
    const newUser = {
      id: Date.now(),
      name: `User ${Date.now()}`,
      email: `user${Date.now()}@example.com`,
    };
    addUser(newUser);
  };

  return (
    <div>
      <h1>User List</h1>
      <button onClick={handleAddUser}>Add User</button>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
            <button onClick={() => removeUser(user.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UserList;

获取整个 store

// src/components/UserProfile.js
import React from 'react';
import useUserStore from '../store/userStore';

function UserProfile({ userId }) {
  const userStore = useUserStore();
  const user = userStore.getUserById(userId);

  if (!user) {
    return <div>User not found</div>;
  }

  return (
    <div>
      <h2>User Profile</h2>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
    </div>
  );
}

export default UserProfile;

4.3 使用中间件

持久化中间件

// src/store/persistStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const usePersistStore = create(
  persist(
    (set) => ({
      count: 0,
      user: {
        name: '',
        email: '',
      },
      increment: () => set((state) => ({ count: state.count + 1 })),
      setUser: (user) => set({ user }),
    }),
    {
      name: 'my-app-storage', // 存储在 localStorage 中的键名
      // 可以自定义存储方式
      // getStorage: () => sessionStorage,
    }
  )
);

export default usePersistStore;

DevTools 中间件

// src/store/devToolsStore.js
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const useDevToolsStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
    }),
    {
      name: 'MyStore', // DevTools 中的名称
    }
  )
);

export default useDevToolsStore;

组合中间件

// src/store/combinedStore.js
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

const useCombinedStore = create(
  devtools(
    persist(
      (set) => ({
        count: 0,
        user: null,
        increment: () => set((state) => ({ count: state.count + 1 })),
        setUser: (user) => set({ user }),
      }),
      {
        name: 'combined-storage',
      }
    ),
    {
      name: 'CombinedStore',
    }
  )
);

export default useCombinedStore;

5. 高级功能

5.1 异步 Action

基本异步 action

// src/store/apiStore.js
import { create } from 'zustand';

const useApiStore = create((set) => ({
  data: null,
  loading: false,
  error: null,
  fetchData: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const data = await response.json();
      set({ data, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

export default useApiStore;

使用 async/await

// src/store/userApiStore.js
import { create } from 'zustand';

const useUserApiStore = create((set, get) => ({
  users: [],
  loading: false,
  error: null,
  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const users = await response.json();
      set({ users, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
  fetchUserById: async (userId) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      const user = await response.json();
      // 将新用户添加到列表中
      set((state) => ({
        users: [...state.users.filter(u => u.id !== userId), user],
        loading: false,
      }));
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

export default useUserApiStore;

5.2 选择器

基本选择器

// src/store/todoStore.js
import { create } from 'zustand';

const useTodoStore = create((set) => ({
  todos: [
    { id: 1, text: 'Learn Zustand', completed: true },
    { id: 2, text: 'Build an app', completed: false },
    { id: 3, text: 'Deploy to production', completed: false },
  ],
  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
    ),
  })),
}));

// 选择器
export const selectCompletedTodos = (state) =>
  state.todos.filter((todo) => todo.completed);

export const selectIncompleteTodos = (state) =>
  state.todos.filter((todo) => !todo.completed);

export const selectTodoById = (id) => (state) =>
  state.todos.find((todo) => todo.id === id);

export default useTodoStore;

在组件中使用选择器

// src/components/TodoList.js
import React from 'react';
import useTodoStore, { selectCompletedTodos, selectIncompleteTodos } from '../store/todoStore';

function TodoList() {
  const todos = useTodoStore((state) => state.todos);
  const addTodo = useTodoStore((state) => state.addTodo);
  const toggleTodo = useTodoStore((state) => state.toggleTodo);
  
  // 使用选择器
  const completedTodos = useTodoStore(selectCompletedTodos);
  const incompleteTodos = useTodoStore(selectIncompleteTodos);

  const [inputText, setInputText] = React.useState('');

  const handleAddTodo = () => {
    if (inputText.trim()) {
      addTodo(inputText);
      setInputText('');
    }
  };

  return (
    <div>
      <h1>Todo List</h1>
      
      <div>
        <input
          type="text"
          value={inputText}
          onChange={(e) => setInputText(e.target.value)}
          placeholder="Add a todo"
        />
        <button onClick={handleAddTodo}>Add</button>
      </div>

      <h2>All Todos ({todos.length})</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{
              textDecoration: todo.completed ? 'line-through' : 'none',
            }}>
              {todo.text}
            </span>
          </li>
        ))}
      </ul>

      <h2>Completed Todos ({completedTodos.length})</h2>
      <ul>
        {completedTodos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>

      <h2>Incomplete Todos ({incompleteTodos.length})</h2>
      <ul>
        {incompleteTodos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

5.3 模块化 Store

创建多个 store

// src/store/index.js
import useCounterStore from './counterStore';
import useUserStore from './userStore';
import useTodoStore from './todoStore';

export {
  useCounterStore,
  useUserStore,
  useTodoStore,
};

在组件中使用多个 store

// src/components/Dashboard.js
import React from 'react';
import { useCounterStore, useUserStore, useTodoStore } from '../store';

function Dashboard() {
  // 从不同 store 中获取状态
  const count = useCounterStore((state) => state.count);
  const userCount = useUserStore((state) => state.users.length);
  const todoCount = useTodoStore((state) => state.todos.length);
  const completedTodoCount = useTodoStore(
    (state) => state.todos.filter((todo) => todo.completed).length
  );

  return (
    <div>
      <h1>Dashboard</h1>
      <div>
        <h2>Counter: {count}</h2>
        <h2>Users: {userCount}</h2>
        <h2>Todos: {todoCount}</h2>
        <h2>Completed Todos: {completedTodoCount}</h2>
      </div>
    </div>
  );
}

export default Dashboard;

5.4 自定义中间件

创建自定义中间件

// src/middleware/loggerMiddleware.js
const loggerMiddleware = (config) => (set, get, api) => {
  // 保存原始的 set 函数
  const originalSet = api.setState;
  
  // 重写 set 函数
  api.setState = (...args) => {
    console.log('Before state update:', get());
    originalSet(...args);
    console.log('After state update:', get());
  };
  
  // 调用原始配置
  return config(set, get, api);
};

export default loggerMiddleware;

使用自定义中间件

// src/store/loggerStore.js
import { create } from 'zustand';
import loggerMiddleware from '../middleware/loggerMiddleware';

const useLoggerStore = create(
  loggerMiddleware((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
    decrement: () => set((state) => ({ count: state.count - 1 })),
  }))
);

export default useLoggerStore;

5.5 TypeScript 支持

使用 TypeScript 创建 store

// src/store/counterStore.ts
import { create } from 'zustand';

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

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 }),
  incrementByAmount: (amount) => set((state) => ({ count: state.count + amount })),
}));

export default useCounterStore;

复杂类型

// src/store/userStore.ts
import { create } from 'zustand';

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

interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
  addUser: (user: Omit<User, 'id'>) => void;
  removeUser: (id: number) => void;
  updateUser: (id: number, updates: Partial<User>) => void;
  fetchUsers: () => Promise<void>;
}

const useUserStore = create<UserState>((set) => ({
  users: [],
  loading: false,
  error: null,
  addUser: (user) => set((state) => ({
    users: [...state.users, { ...user, id: Date.now() }],
  })),
  removeUser: (id) => set((state) => ({
    users: state.users.filter((user) => user.id !== id),
  })),
  updateUser: (id, updates) => set((state) => ({
    users: state.users.map((user) =>
      user.id === id ? { ...user, ...updates } : user
    ),
  })),
  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const users = await response.json();
      set({ users, loading: false });
    } catch (error) {
      set({ error: (error as Error).message, loading: false });
    }
  },
}));

export default useUserStore;

6. 实用案例

6.1 购物车应用

创建购物车 store

// src/store/cartStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useCartStore = create(
  persist(
    (set, get) => ({
      items: [],
      addToCart: (product) => set((state) => {
        const existingItem = state.items.find((item) => item.id === product.id);
        if (existingItem) {
          return {
            items: state.items.map((item) =>
              item.id === product.id
                ? { ...item, quantity: item.quantity + 1 }
                : item
            ),
          };
        }
        return {
          items: [...state.items, { ...product, quantity: 1 }],
        };
      }),
      removeFromCart: (productId) => set((state) => ({
        items: state.items.filter((item) => item.id !== productId),
      })),
      updateQuantity: (productId, quantity) => set((state) => ({
        items: state.items.map((item) =>
          item.id === productId ? { ...item, quantity } : item
        ),
      })),
      clearCart: () => set({ items: [] }),
      getTotalItems: () => {
        const { items } = get();
        return items.reduce((total, item) => total + item.quantity, 0);
      },
      getTotalPrice: () => {
        const { items } = get();
        return items.reduce((total, item) => total + item.price * item.quantity, 0);
      },
    }),
    {
      name: 'cart-storage',
    }
  )
);

export default useCartStore;

创建购物车组件

// src/components/Cart.js
import React from 'react';
import useCartStore from '../store/cartStore';

function Cart() {
  const items = useCartStore((state) => state.items);
  const removeFromCart = useCartStore((state) => state.removeFromCart);
  const updateQuantity = useCartStore((state) => state.updateQuantity);
  const clearCart = useCartStore((state) => state.clearCart);
  const getTotalItems = useCartStore((state) => state.getTotalItems);
  const getTotalPrice = useCartStore((state) => state.getTotalPrice);

  const totalItems = getTotalItems();
  const totalPrice = getTotalPrice();

  if (items.length === 0) {
    return <div>Your cart is empty</div>;
  }

  return (
    <div>
      <h1>Shopping Cart</h1>
      <button onClick={clearCart}>Clear Cart</button>
      <h2>Total Items: {totalItems}</h2>
      <h2>Total Price: ${totalPrice.toFixed(2)}</h2>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <h3>{item.name}</h3>
            <p>Price: ${item.price.toFixed(2)}</p>
            <div>
              <button onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}>-</button>
              <span>Quantity: {item.quantity}</span>
              <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
            </div>
            <button onClick={() => removeFromCart(item.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Cart;

创建产品列表组件

// src/components/ProductList.js
import React from 'react';
import useCartStore from '../store/cartStore';

// 模拟产品数据
const products = [
  { id: 1, name: 'Product 1', price: 10.99 },
  { id: 2, name: 'Product 2', price: 19.99 },
  { id: 3, name: 'Product 3', price: 5.99 },
  { id: 4, name: 'Product 4', price: 29.99 },
];

function ProductList() {
  const addToCart = useCartStore((state) => state.addToCart);

  return (
    <div>
      <h1>Products</h1>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap' }}>
        {products.map((product) => (
          <div key={product.id} style={{ border: '1px solid #ccc', padding: '10px', width: '200px' }}>
            <h2>{product.name}</h2>
            <p>Price: ${product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)}>Add to Cart</button>
          </div>
        ))}
      </div>
    </div>
  );
}

export default ProductList;

6.2 认证状态管理

创建认证 store

// src/store/authStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useAuthStore = create(
  persist(
    (set) => ({
      user: null,
      token: null,
      loading: false,
      error: null,
      login: async (email, password) => {
        set({ loading: true, error: null });
        try {
          // 模拟登录 API 调用
          await new Promise((resolve) => setTimeout(resolve, 1000));
          const mockUser = {
            id: 1,
            name: 'John Doe',
            email: email,
          };
          const mockToken = 'mock-token-123';
          set({ user: mockUser, token: mockToken, loading: false });
          return true;
        } catch (error) {
          set({ error: 'Invalid email or password', loading: false });
          return false;
        }
      },
      logout: () => set({ user: null, token: null }),
      updateUser: (updates) => set((state) => ({
        user: state.user ? { ...state.user, ...updates } : null,
      })),
    }),
    {
      name: 'auth-storage',
      // 只持久化 user 和 token
      partialize: (state) => ({ user: state.user, token: state.token }),
    }
  )
);

export default useAuthStore;

创建登录组件

// src/components/Login.js
import React, { useState } from 'react';
import useAuthStore from '../store/authStore';

function Login() {
  const login = useAuthStore((state) => state.login);
  const loading = useAuthStore((state) => state.loading);
  const error = useAuthStore((state) => state.error);

  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async (e) => {
    e.preventDefault();
    await login(email, password);
  };

  return (
    <div>
      <h1>Login</h1>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <form onSubmit={handleLogin}>
        <div>
          <label>Email:</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Password:</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        </div>
        <button type="submit" disabled={loading}>
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
}

export default Login;

创建用户信息组件

// src/components/UserProfile.js
import React from 'react';
import useAuthStore from '../store/authStore';

function UserProfile() {
  const user = useAuthStore((state) => state.user);
  const logout = useAuthStore((state) => state.logout);
  const updateUser = useAuthStore((state) => state.updateUser);

  if (!user) {
    return <div>Please login to see your profile</div>;
  }

  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <button onClick={logout}>Logout</button>
      <button onClick={() => updateUser({ name: 'Updated Name' })}>
        Update Name
      </button>
    </div>
  );
}

export default UserProfile;

7. 性能优化

7.1 选择性订阅

只订阅需要的状态

// 不好的做法:订阅整个状态
const store = useStore(); // 会在任何状态变化时重新渲染

// 好的做法:选择性订阅
const count = useStore((state) => state.count); // 只在 count 变化时重新渲染
const increment = useStore((state) => state.increment); // 函数引用稳定,不会导致重新渲染

使用 useShallow 优化对象订阅

// src/components/OptimizedComponent.js
import React from 'react';
import { useShallow } from 'zustand/react/shallow';
import useCounterStore from '../store/counterStore';

function OptimizedComponent() {
  // 使用 useShallow 进行浅层比较
  const { count, increment, decrement } = useCounterStore(
    useShallow((state) => ({
      count: state.count,
      increment: state.increment,
      decrement: state.decrement,
    }))
  );

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default OptimizedComponent;

7.2 避免在渲染中创建新函数

不好的做法

// 不好的做法:每次渲染都会创建新的选择器函数
function BadComponent() {
  const todos = useTodoStore((state) =>
    state.todos.filter((todo) => todo.completed)
  );
  // ...
}

好的做法

// 好的做法:将选择器提取到组件外部
const selectCompletedTodos = (state) =>
  state.todos.filter((todo) => todo.completed);

function GoodComponent() {
  const todos = useTodoStore(selectCompletedTodos);
  // ...
}

7.3 使用 memo 包装组件

// src/components/MemoizedComponent.js
import React, { memo } from 'react';
import useCounterStore from '../store/counterStore';

const MemoizedCounter = memo(() => {
  const count = useCounterStore((state) => state.count);
  console.log('MemoizedCounter rendered');
  return <div>Count: {count}</div>;
});

function MemoizedComponent() {
  const increment = useCounterStore((state) => state.increment);
  return (
    <div>
      <h1>Memoized Component</h1>
      <MemoizedCounter />
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default MemoizedComponent;

8. 最佳实践

8.1 项目结构

  • 按功能组织 store:将相关的状态和操作放在一个 store 中
  • 使用模块化导出:通过 index.js 导出所有 store
  • 分离业务逻辑:将复杂的业务逻辑从组件中移到 store 中
  • 使用选择器:创建可重用的选择器函数

8.2 Store 设计

  • 保持 store 简洁:每个 store 只负责一个领域的状态
  • 使用不可变更新:遵循不可变数据原则
  • 提供清晰的 API:为每个操作提供明确的方法
  • 处理异步操作:在 store 中处理异步逻辑
  • 添加适当的状态:包括 loading、error 等状态

8.3 组件使用

  • 选择性订阅:只订阅组件需要的状态
  • 使用 useShallow:对于对象和数组的订阅,使用 useShallow 进行优化
  • 避免在渲染中创建新函数:将选择器提取到组件外部
  • 使用 memo:对于纯展示组件,使用 React.memo 进行优化

8.4 中间件使用

  • 使用持久化中间件:对于需要持久化的状态,使用 persist 中间件
  • 使用 DevTools 中间件:在开发环境中使用 devtools 中间件
  • 创建自定义中间件:对于特定的需求,创建自定义中间件

8.5 测试

  • 测试 store 逻辑:测试状态更新和异步操作
  • 测试选择器:测试选择器是否正确提取数据
  • 测试组件:测试组件与 store 的交互

9. 常见问题与解决方案

9.1 状态不更新

问题:调用 action 后状态没有更新

解决方案

  • 检查 action 是否正确调用
  • 确保在 set 函数中返回新的状态对象
  • 对于嵌套状态,确保使用不可变更新

9.2 组件不重新渲染

问题:状态更新后组件没有重新渲染

解决方案

  • 检查是否正确订阅了状态
  • 确保订阅的状态确实发生了变化
  • 对于对象和数组,确保返回新的引用

9.3 持久化不工作

问题:使用 persist 中间件后状态没有持久化

解决方案

  • 检查存储键名是否正确
  • 确保浏览器支持 localStorage
  • 检查状态是否可序列化

9.4 异步操作错误

问题:异步 action 失败或不执行

解决方案

  • 检查异步函数是否正确使用 async/await
  • 确保在异步操作中正确处理错误
  • 检查网络请求是否正确

9.5 多个 store 之间的通信

问题:需要在一个 store 中访问另一个 store 的状态

解决方案

  • 将一个 store 的引用传递给另一个 store
  • 使用事件系统进行通信
  • 将共享状态提取到一个单独的 store 中

10. 参考资源

10.1 官方文档

10.2 学习资源

10.3 工具与库

10.4 相关库

  • Jotai - 基于原子的状态管理库
  • Valtio - 基于代理的状态管理库
  • Redux - 传统状态管理库

11. 总结

Zustand 是一个轻量级、简单易用的状态管理库,它提供了一种直观的方式来管理 React 应用的状态。通过本教程,你应该已经掌握了 Zustand 的基本使用方法和高级功能,包括:

  • 核心概念(store、set、get)
  • 基本使用
  • 中间件(persist、devtools)
  • 异步 action
  • 选择器
  • 模块化 store
  • 自定义中间件
  • TypeScript 支持
  • 性能优化
  • 最佳实践

Zustand 的设计理念是 "简单至上",它通过最小化 API 表面和避免不必要的复杂性,为开发者提供了一种简洁、高效的状态管理方案。与其他状态管理库相比,Zustand 具有以下优势:

  • 轻量级:核心代码只有几百行,无依赖
  • 简单易用:API 简洁明了,学习曲线平缓
  • 无需 Provider:不需要在应用顶层包裹 Provider
  • 性能优异:避免不必要的渲染
  • 灵活多变:支持中间件、选择器等高级功能

Zustand 适合从简单的个人项目到复杂的企业级应用的各种场景。它的灵活性和可扩展性使其成为 React 生态系统中一个重要的状态管理解决方案。

随着你对 Zustand 的深入了解,你会发现它不仅是一个状态管理库,更是一种开发理念:通过简洁的 API 和清晰的架构,让状态管理变得更加简单和可预测。希望本教程对你的学习和开发有所帮助!

« 上一篇 Redux 教程 - JavaScript 应用的可预测状态容器 下一篇 » Jotai 教程 - 基于原子的状态管理库