React 状态管理库 Zustand 深度解析

在 React 生态系统中,状态管理一直是开发者关注的重点。从早期的 Redux 到后来的 MobX、Recoil,再到今天要介绍的 Zustand,每个库都有其独特的设计理念和适用场景。本文将深入探讨 Zustand 的使用方法、优缺点,分析其源码实现原理,并最终手把手教你实现一个类似的状态管理库。

什么是 Zustand

Zustand(德语中的 "状态")是一个小巧、快速且可扩展的状态管理解决方案。它由 Poimandres 团队开发,以其简洁的 API 和出色的性能而闻名。

核心特性

  • 轻量级:压缩后仅 2.9kb
  • 无样板代码:不需要 providers、reducers 或 actions
  • TypeScript 友好:完整的类型推断支持
  • 中间件支持:内置 devtools、persist 等中间件
  • 框架无关:可在 React、Vue、Vanilla JS 中使用

基础使用

安装

npm install zustand
# 或
yarn add zustand
# 或
pnpm add zustand

创建 Store

import { create } from 'zustand';

// 定义状态类型
interface BearState {
  bears: number;
  increase: (by: number) => void;
  decrease: (by: number) => void;
  reset: () => void;
}

// 创建 store
const useBearStore = create<BearState>()((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
  decrease: (by) => set((state) => ({ bears: state.bears - by })),
  reset: () => set({ bears: 0 })
}));

在组件中使用

import React from 'react';
import { useBearStore } from './store';

function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} around here...</h1>;
}

function Controls() {
  const increase = useBearStore((state) => state.increase);
  const decrease = useBearStore((state) => state.decrease);
  const reset = useBearStore((state) => state.reset);

  return (
    <div>
      <button onClick={() => increase(1)}>one up</button>
      <button onClick={() => decrease(1)}>one down</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

高级用法

异步操作

interface UserState {
  users: User[];
  loading: boolean;
  fetchUsers: () => Promise<void>;
}

const useUserStore = create<UserState>()((set, get) => ({
  users: [],
  loading: false,
  fetchUsers: async () => {
    set({ loading: true });
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      set({ users, loading: false });
    } catch (error) {
      set({ loading: false });
      console.error('Failed to fetch users:', error);
    }
  }
}));

订阅状态变化

// 订阅整个 store
const unsubscribe = useBearStore.subscribe((state) =>
  console.log('State changed:', state)
);

// 订阅特定状态
const unsubscribeBears = useBearStore.subscribe(
  (state) => state.bears,
  (bears) => console.log('Bears count:', bears)
);

// 清理订阅
unsubscribe();
unsubscribeBears();

中间件使用

Persist 中间件

import { persist } from 'zustand/middleware';

const usePersistedStore = create(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    }),
    {
      name: 'counter-storage', // localStorage key
      storage: createJSONStorage(() => localStorage)
    }
  )
);

DevTools 中间件

import { devtools } from 'zustand/middleware';

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

与其他状态管理库对比

vs Redux

特性ZustandRedux
包大小2.9kb11.2kb (含 React-Redux)
样板代码极少较多
学习曲线平缓陡峭
TypeScript 支持原生支持需要额外配置
中间件生态内置常用中间件丰富的第三方中间件
时间旅行调试支持原生支持

Zustand 优势:

  • 更简洁的 API,无需 providers 和 reducers
  • 更小的包体积
  • 更好的 TypeScript 体验
  • 更少的样板代码

Redux 优势:

  • 更成熟的生态系统
  • 更强大的调试工具
  • 更适合大型应用的状态管理
  • 更多的学习资源和社区支持

vs Context API

特性ZustandContext API
性能优秀(精确订阅)一般(全量更新)
使用复杂度简单中等
嵌套地狱可能存在
状态共享全局需要 Provider 包裹

Zustand 优势:

  • 避免不必要的重渲染
  • 无需 Provider 包裹
  • 更好的性能表现

Context API 优势:

  • React 内置,无需额外依赖
  • 更符合 React 的设计理念

vs Recoil

特性ZustandRecoil
稳定性稳定实验性
学习成本中等
原子化状态不支持原生支持
异步处理简单复杂但强大

源码分析

让我们深入 Zustand 的源码,了解其实现原理。

核心架构

Zustand 的核心由以下几个部分组成:

  1. Store 创建器create 函数
  2. 状态管理器StoreApi 接口
  3. React 集成useStore hook
  4. 订阅系统:观察者模式实现

create 函数实现

// 简化版的 create 函数实现
type StateCreator<T> = (set: SetState<T>, get: GetState<T>) => T;

function create<T>(stateCreator: StateCreator<T>) {
  // 创建 store API
  const api = createStore(stateCreator);

  // 返回 React hook
  const useStore = <U>(selector?: (state: T) => U) => {
    return useStoreImpl(api, selector);
  };

  // 将 API 方法附加到 hook 上
  Object.assign(useStore, api);

  return useStore;
}

createStore 实现

interface StoreApi<T> {
  setState: (partial: T | Partial<T> | ((state: T) => T | Partial<T>)) => void;
  getState: () => T;
  subscribe: (listener: (state: T, prevState: T) => void) => () => void;
  destroy: () => void;
}

function createStore<T>(createState: StateCreator<T>): StoreApi<T> {
  let state: T;
  const listeners = new Set<(state: T, prevState: T) => void>();

  const setState = (partial: any) => {
    const nextState = typeof partial === 'function' ? partial(state) : partial;
    if (!Object.is(nextState, state)) {
      const previousState = state;
      state = Object.assign({}, state, nextState);
      listeners.forEach((listener) => listener(state, previousState));
    }
  };

  const getState = () => state;

  const subscribe = (listener: (state: T, prevState: T) => void) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };

  const destroy = () => {
    listeners.clear();
  };

  const api = { setState, getState, subscribe, destroy };

  // 初始化状态
  state = createState(setState, getState);

  return api;
}

React Hook 实现

function useStoreImpl<T, U>(api: StoreApi<T>, selector?: (state: T) => U) {
  const [, forceUpdate] = useReducer((c) => c + 1, 0);

  const state = api.getState();
  const selectedState = selector ? selector(state) : state;

  const selectedStateRef = useRef(selectedState);
  const selectorRef = useRef(selector);
  const erroredRef = useRef(false);

  // 更新引用
  if (selector !== selectorRef.current) {
    const newSelectedState = selector ? selector(state) : state;
    if (!Object.is(newSelectedState, selectedStateRef.current)) {
      selectedStateRef.current = newSelectedState;
    }
    selectorRef.current = selector;
  }

  useLayoutEffect(() => {
    const listener = () => {
      try {
        const nextState = api.getState();
        const nextSelectedState = selectorRef.current
          ? selectorRef.current(nextState)
          : nextState;

        if (!Object.is(nextSelectedState, selectedStateRef.current)) {
          selectedStateRef.current = nextSelectedState;
          forceUpdate();
        }
      } catch (error) {
        erroredRef.current = true;
        forceUpdate();
      }
    };

    const unsubscribe = api.subscribe(listener);
    return unsubscribe;
  }, []);

  return selectedStateRef.current;
}

中间件系统

Zustand 的中间件系统基于函数组合:

type Middleware<T> = (config: StateCreator<T>) => StateCreator<T>;

// persist 中间件简化实现
const persist = <T>(
  config: StateCreator<T>,
  options: PersistOptions
): StateCreator<T> => {
  return (set, get) => {
    // 从存储中恢复状态
    const restoredState = options.storage.getItem(options.name);

    const store = config((partial) => {
      set(partial);
      // 保存到存储
      options.storage.setItem(options.name, get());
    }, get);

    return {
      ...store,
      ...restoredState
    };
  };
};

手写一个简化版 Zustand

基于对源码的理解,让我们实现一个简化版的状态管理库:

// types.ts
export type SetState<T> = (
  partial: T | Partial<T> | ((state: T) => T | Partial<T>)
) => void

export type GetState<T> = () => T

export type StateCreator<T> = (set: SetState<T>, get: GetState<T>) => T

export interface StoreApi<T> {
  setState: SetState<T>
  getState: GetState<T>
  subscribe: (listener: (state: T, prevState: T) => void) => () => void
  destroy: () => void
}

// store.ts
import { StoreApi, StateCreator, SetState, GetState } from './types'

/**
 * 创建状态存储
 * @param createState 状态创建函数
 * @returns 状态存储 API
 */
export function createStore<T>(createState: StateCreator<T>): StoreApi<T> {
  let state: T
  const listeners = new Set<(state: T, prevState: T) => void>()

  /**
   * 设置状态
   * @param partial 部分状态或状态更新函数
   */
  const setState: SetState<T> = (partial) => {
    const nextState = typeof partial === 'function'
      ? (partial as (state: T) => T | Partial<T>)(state)
      : partial

    if (!Object.is(nextState, state)) {
      const previousState = state
      state = Object.assign({}, state, nextState)

      // 通知所有监听器
      listeners.forEach((listener) => {
        listener(state, previousState)
      })
    }
  }

  /**
   * 获取当前状态
   * @returns 当前状态
   */
  const getState: GetState<T> = () => state

  /**
   * 订阅状态变化
   * @param listener 状态变化监听器
   * @returns 取消订阅函数
   */
  const subscribe = (listener: (state: T, prevState: T) => void) => {
    listeners.add(listener)
    return () => listeners.delete(listener)
  }

  /**
   * 销毁存储
   */
  const destroy = () => {
    listeners.clear()
  }

  const api: StoreApi<T> = {
    setState,
    getState,
    subscribe,
    destroy
  }

  // 初始化状态
  state = createState(setState, getState)

  return api
}

// react.ts
import { useLayoutEffect, useReducer, useRef } from 'react'
import { StoreApi } from './types'

/**
 * React Hook 实现
 * @param api 状态存储 API
 * @param selector 状态选择器
 * @returns 选中的状态
 */
export function useStore<T, U>(
  api: StoreApi<T>,
  selector?: (state: T) => U
): U extends undefined ? T : U {
  const [, forceUpdate] = useReducer((c: number) => c + 1, 0)

  const state = api.getState()
  const selectedState = selector ? selector(state) : state

  const selectedStateRef = useRef(selectedState)
  const selectorRef = useRef(selector)

  // 更新选择器引用
  if (selector !== selectorRef.current) {
    const newSelectedState = selector ? selector(state) : state
    if (!Object.is(newSelectedState, selectedStateRef.current)) {
      selectedStateRef.current = newSelectedState
    }
    selectorRef.current = selector
  }

  useLayoutEffect(() => {
    /**
     * 状态变化监听器
     */
    const listener = () => {
      try {
        const nextState = api.getState()
        const nextSelectedState = selectorRef.current
          ? selectorRef.current(nextState)
          : nextState

        if (!Object.is(nextSelectedState, selectedStateRef.current)) {
          selectedStateRef.current = nextSelectedState
          forceUpdate()
        }
      } catch (error) {
        console.error('State update error:', error)
        forceUpdate()
      }
    }

    const unsubscribe = api.subscribe(listener)
    return unsubscribe
  }, [])

  return selectedStateRef.current as any
}

// create.ts
import { createStore } from './store'
import { useStore } from './react'
import { StateCreator, StoreApi } from './types'

/**
 * 创建状态管理 Hook
 * @param stateCreator 状态创建函数
 * @returns 状态管理 Hook
 */
export function create<T>(
  stateCreator: StateCreator<T>
) {
  const api = createStore(stateCreator)

  const useStoreHook = <U>(selector?: (state: T) => U) => {
    return useStore(api, selector)
  }

  // 将 API 方法附加到 hook 上
  Object.assign(useStoreHook, api)

  return useStoreHook as typeof useStoreHook & StoreApi<T>
}

// 使用示例
interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset: () => 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 }),
}))

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

添加中间件支持

// middleware.ts
import { StateCreator } from './types';

export type Middleware<T> = (config: StateCreator<T>) => StateCreator<T>;

/**
 * 日志中间件
 * @param config 状态创建函数
 * @returns 包装后的状态创建函数
 */
export const logger = <T>(config: StateCreator<T>): StateCreator<T> => {
  return (set, get) => {
    const loggedSet = (partial: any) => {
      const prevState = get();
      set(partial);
      const nextState = get();
      console.log('State changed:', { prevState, nextState });
    };

    return config(loggedSet, get);
  };
};

/**
 * 持久化中间件
 * @param config 状态创建函数
 * @param options 持久化选项
 * @returns 包装后的状态创建函数
 */
export const persist = <T>(
  config: StateCreator<T>,
  options: { name: string; storage?: Storage }
): StateCreator<T> => {
  const { name, storage = localStorage } = options;

  return (set, get) => {
    // 恢复保存的状态
    let restoredState: Partial<T> = {};
    try {
      const saved = storage.getItem(name);
      if (saved) {
        restoredState = JSON.parse(saved);
      }
    } catch (error) {
      console.error('Failed to restore state:', error);
    }

    const persistedSet = (partial: any) => {
      set(partial);
      try {
        storage.setItem(name, JSON.stringify(get()));
      } catch (error) {
        console.error('Failed to persist state:', error);
      }
    };

    const store = config(persistedSet, get);

    return {
      ...store,
      ...restoredState
    };
  };
};

// 使用中间件
const usePersistedCounterStore = create(
  persist(
    logger((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }))
    })),
    { name: 'counter-storage' }
  )
);

性能优化技巧

1. 精确订阅

// ❌ 不好的做法 - 订阅整个状态
const { count, users, settings } = useStore();

// ✅ 好的做法 - 只订阅需要的状态
const count = useStore((state) => state.count);
const users = useStore((state) => state.users);

2. 使用 shallow 比较

import { shallow } from 'zustand/shallow';

// 对于对象或数组,使用 shallow 比较
const { increment, decrement } = useStore(
  (state) => ({ increment: state.increment, decrement: state.decrement }),
  shallow
);

3. 状态分片

// 将大的状态分解为多个小的 store
const useUserStore = create((set) => ({
  users: [],
  addUser: (user) => set((state) => ({ users: [...state.users, user] }))
}));

const useSettingsStore = create((set) => ({
  theme: 'light',
  setTheme: (theme) => set({ theme })
}));

最佳实践

1. 类型安全

// 定义清晰的接口
interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
  addTodo: (text: string) => void;
  toggleTodo: (id: string) => void;
  setFilter: (filter: TodoState['filter']) => void;
}

// 使用泛型确保类型安全
const useTodoStore = create<TodoState>()((set) => ({
  todos: [],
  filter: 'all',
  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: nanoid(), text, completed: false }]
    })),
  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    })),
  setFilter: (filter) => set({ filter })
}));

2. 异步操作处理

interface ApiState {
  data: any[];
  loading: boolean;
  error: string | null;
  fetchData: () => Promise<void>;
}

const useApiStore = create<ApiState>()((set, get) => ({
  data: [],
  loading: false,
  error: null,
  fetchData: async () => {
    // 避免重复请求
    if (get().loading) return;

    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/data');
      if (!response.ok) throw new Error('Failed to fetch');
      const data = await response.json();
      set({ data, loading: false });
    } catch (error) {
      set({
        error: error instanceof Error ? error.message : 'Unknown error',
        loading: false
      });
    }
  }
}));

3. 测试友好

// 导出 store 创建函数,便于测试
export const createBearStore = () =>
  create<BearState>()((set) => ({
    bears: 0,
    increase: (by) => set((state) => ({ bears: state.bears + by })),
    reset: () => set({ bears: 0 })
  }));

export const useBearStore = createBearStore();

// 测试
import { act, renderHook } from '@testing-library/react';
import { createBearStore } from './bearStore';

test('should increase bear count', () => {
  const store = createBearStore();
  const { result } = renderHook(() => store());

  act(() => {
    result.current.increase(2);
  });

  expect(result.current.bears).toBe(2);
});

总结

Zustand 作为一个现代化的状态管理库,以其简洁的 API、优秀的性能和强大的功能赢得了开发者的青睐。通过本文的深入分析,我们了解了:

  1. Zustand 的核心优势:轻量级、无样板代码、TypeScript 友好
  2. 与其他库的对比:在不同场景下的适用性
  3. 源码实现原理:基于观察者模式的状态管理机制
  4. 手写实现:理解核心概念并实现简化版本
  5. 最佳实践:如何在实际项目中高效使用

选择状态管理库时,需要根据项目规模、团队技术栈和具体需求来决定。Zustand 特别适合中小型项目或者希望减少样板代码的场景。对于大型企业级应用,Redux 可能仍然是更好的选择。

无论选择哪种方案,理解其底层原理都有助于我们更好地使用这些工具,写出更高质量的代码。