[临时摘要] React 状态管理库 Zustand 深度解析 - 请在开发环境中运行 pnpm generate 生成正式摘要
在 React 生态系统中,状态管理一直是开发者关注的重点。从早期的 Redux 到后来的 MobX、Recoil,再到今天要介绍的 Zustand,每个库都有其独特的设计理念和适用场景。本文将深入探讨 Zustand 的使用方法、优缺点,分析其源码实现原理,并最终手把手教你实现一个类似的状态管理库。
Zustand(德语中的 "状态")是一个小巧、快速且可扩展的状态管理解决方案。它由 Poimandres 团队开发,以其简洁的 API 和出色的性能而闻名。
npm install zustand
# 或
yarn add zustand
# 或
pnpm add zustand
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();
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)
}
)
);
import { devtools } from 'zustand/middleware';
const useStore = create(
devtools(
(set) => ({
count: 0,
increment: () =>
set((state) => ({ count: state.count + 1 }), false, 'increment')
}),
{
name: 'counter-store'
}
)
);
| 特性 | Zustand | Redux |
|---|---|---|
| 包大小 | 2.9kb | 11.2kb (含 React-Redux) |
| 样板代码 | 极少 | 较多 |
| 学习曲线 | 平缓 | 陡峭 |
| TypeScript 支持 | 原生支持 | 需要额外配置 |
| 中间件生态 | 内置常用中间件 | 丰富的第三方中间件 |
| 时间旅行调试 | 支持 | 原生支持 |
Zustand 优势:
Redux 优势:
| 特性 | Zustand | Context API |
|---|---|---|
| 性能 | 优秀(精确订阅) | 一般(全量更新) |
| 使用复杂度 | 简单 | 中等 |
| 嵌套地狱 | 无 | 可能存在 |
| 状态共享 | 全局 | 需要 Provider 包裹 |
Zustand 优势:
Context API 优势:
| 特性 | Zustand | Recoil |
|---|---|---|
| 稳定性 | 稳定 | 实验性 |
| 学习成本 | 低 | 中等 |
| 原子化状态 | 不支持 | 原生支持 |
| 异步处理 | 简单 | 复杂但强大 |
让我们深入 Zustand 的源码,了解其实现原理。
Zustand 的核心由以下几个部分组成:
create 函数StoreApi 接口useStore hook// 简化版的 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;
}
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;
}
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
};
};
};
基于对源码的理解,让我们实现一个简化版的状态管理库:
// 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' }
)
);
// ❌ 不好的做法 - 订阅整个状态
const { count, users, settings } = useStore();
// ✅ 好的做法 - 只订阅需要的状态
const count = useStore((state) => state.count);
const users = useStore((state) => state.users);
import { shallow } from 'zustand/shallow';
// 对于对象或数组,使用 shallow 比较
const { increment, decrement } = useStore(
(state) => ({ increment: state.increment, decrement: state.decrement }),
shallow
);
// 将大的状态分解为多个小的 store
const useUserStore = create((set) => ({
users: [],
addUser: (user) => set((state) => ({ users: [...state.users, user] }))
}));
const useSettingsStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme })
}));
// 定义清晰的接口
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 })
}));
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
});
}
}
}));
// 导出 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、优秀的性能和强大的功能赢得了开发者的青睐。通过本文的深入分析,我们了解了:
选择状态管理库时,需要根据项目规模、团队技术栈和具体需求来决定。Zustand 特别适合中小型项目或者希望减少样板代码的场景。对于大型企业级应用,Redux 可能仍然是更好的选择。
无论选择哪种方案,理解其底层原理都有助于我们更好地使用这些工具,写出更高质量的代码。