Skip to main content

zustand

Zustand 是一个轻量级的 React 状态管理库,其核心原理可以用“三个层、两个核心、一套扩展”来概括。下面从面试高频考点出发,逐步拆解。


一、整体架构:三层设计

Zustand 源码采用分层架构,主要包含三个核心文件:

  1. vanilla.ts:框架无关的核心状态管理逻辑,可在任何 JavaScript 环境(甚至 Node.js)运行
  2. react.ts:React 集成层,连接 Zustand store 与 React 组件
  3. middleware:中间件系统,提供日志、持久化、调试等扩展能力

这种设计的妙处在于将“状态管理核心逻辑”与“UI 框架绑定”彻底分离,使 store 成为与框架无关的纯 JavaScript 对象。下面逐一深入每个层面。

二、vanilla 层:核心状态管理(面试最常问)

Zustand 的核心状态管理逻辑非常精简,核心函数 createStoreImpl 的实现大致如下:

const createStoreImpl = (createState) => {
let state; // 1. 闭包存储当前状态
const listeners = new Set(); // 2. Set 存储所有订阅者

// 3. 状态更新函数
const setState = (partial, replace) => {
const nextState = typeof partial === 'function' ? partial(state) : partial;
if (!Object.is(nextState, state)) { // 浅比较,无变化则跳过
const previousState = state;
// 完全替换 or 浅合并
state = replace ?? (typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState);
// 遍历通知所有监听器
listeners.forEach(listener => listener(state, previousState));
}
};

// 4. 状态获取
const getState = () => state;

// 5. 订阅管理
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener); // 返回取消订阅函数
};

const api = { setState, getState, subscribe, getInitialState };
const initialState = (state = createState(setState, getState, api));
return api;
};

面试中需要说清楚以下几个关键点

1. 状态存储:闭包 let state

Zustand 没有使用 Redux 那样的全局 store 对象,而是通过 JavaScript 闭包createStore 函数内部用 let state 变量保存状态。调用 getState() 时直接返回这个变量,调用 setState() 时更新它。所有订阅该 store 的函数都能通过闭包访问到这个变量。

2. 订阅管理:Set 存储监听器

Zustand 使用 new Set() 来存储所有监听器(listeners)。当调用 subscribe(listener) 时,listener 被添加到 Set 中;取消订阅时调用返回的删除函数即可。Set 的两个特性在这一场景下非常契合:一是自动去重(同一个组件不会重复订阅),二是遍历效率高(forEach 无需像数组那样担心 index 变化)。

3. 状态更新:浅比较与选择性通知

setState 在执行更新前会用 Object.is(类似 ===,但能区分 +0-0NaN 相等)比较新旧状态:

  • 无变化:跳过更新,不会触发重渲染(性能优化)
  • 有变化:更新 state 变量,然后遍历 listeners 依次调用,传入新旧状态

4. 发布-订阅 vs EventBus

Zustand 采用 数据驱动的全员广播模式:核心数据结构是 Set<Callback>,状态变更后直接遍历调用所有订阅者,不依赖中间的事件分发层。这与 EventBus(基于 Map<EventName, Callback[]> 的精确定位分发)不同:

对比维度EventBus(事件总线)Zustand
数据结构Map<EventName, Callback[]>Set<Callback>
分发方式根据 key 查找 → 精确分发无查找 → 全员广播
驱动模式动作驱动(Action-driven)数据驱动(Data-driven)
典型场景“点击保存”、“请求完成”“用户信息更新”、“计数器变化”

简单来说:EventBus 是分频道的广播站(你要听哪个频道,就把你加到那个频道的名单里);Zustand 是班级里的黑板报(数据写在黑板上,所有人都会去看,至于看不看具体内容由自己决定)。

三、react.ts 层:React 集成原理

Zustand 的 React 绑定建立在一个关键 API 之上——useSyncExternalStore(React 18 引入)。

为什么不直接使用 useState + useEffect

useSyncExternalStore 是 React 官方为外部 store 订阅提供的高阶 hook,它能:

  • 安全地在 React 的并发渲染模式(Concurrent Rendering)下订阅外部数据源
  • 保证 store 状态变更与 React 渲染流程的一致性
  • 通过 getSnapshot 参数解决”tearing“(撕裂)问题

create 函数的工作流程

用户调用的 create 函数最终生成一个 双重身份useBoundStore

  1. 调用 createStore 创建框架无关的 vanilla store API
  2. 创建 useBoundStore 函数,内部调用 useStore(基于 useSyncExternalStore 的封装)
  3. 通过 Object.assign 将 store API 方法(setStategetStatesubscribe)合并到 useBoundStore
  4. 返回合并后的函数,既可以当 hook 用:useBoundStore(),也可以直接调用 API:useBoundStore.getState()
// 使用时,两种调用方式:
const count = useBoundStore(state => state.count); // 作为 Hook
useBoundStore.setState({ count: 1 }); // 直接调用 API

选择性订阅与性能优化

Zustand 支持通过 selector 函数 选择性订阅状态片段:

const count = useStore(state => state.count); // 仅订阅 count
const { count, name } = useStore(state => ({ count: state.count, name: state.name })); // 订阅多个

只有 selector 返回的值变化时,组件才会重新渲染。默认使用 Object.is 进行严格相等比较;对于返回对象/数组的 selector,可以使用 shallow 比较函数进行浅比较优化。

四、中间件系统:洋葱模型

Zustand 的中间件本质是 高阶函数(Higher-Order Function) ——对原始的 createState 函数进行层层包装。官方文档称之为”增强器(enhancer)“。

设计哲学:与 Redux 中间件的区别

对比维度ReduxZustand
中间件形式管道(pipeline)增强器(enhancer)
拦截对象拦截 dispatch 的 action包装 createState 函数
扩展方式多个中间件串联成处理链高阶函数层层嵌套(洋葱模型)
核心原理next(action) 链式传递setgetapi 进行包装改造

Redux 中间件像一个管道,一个 action 流入,经过一系列中间件处理后,最终到达 reducer;Zustand 中间件则像在 create 函数外面一层层套壳,每一层都能对 setget 进行增强。

核心模式:包装 set 函数

以官方 logger 中间件为例:

const logger = (createState) => (set, get, api) => {
const setWithLogging = (...args) => {
console.log('prev state:', get());
const result = set(...args);
console.log('next state:', get());
return result;
};
// 将包装后的 set 传入原始 createState
return createState(setWithLogging, get, api);
};

Zustand 的中间件通过setgetapi 进行拦截和包装,实现了日志记录、持久化、Immer 可变更新等一系列功能,而 Zustand 核心代码对此浑然不知——这就是函数组合控制反转的设计之美。这种设计使得中间件之间可以任意组合,形成“洋葱模型”的执行顺序:从外向内穿透经过层层中间件,到达核心 store 后,响应再从内向外返回。

五、与 Redux 的核心区别(面试加分点)

  1. 无需 Provider:Zustand 不需要用 Context Provider 包裹整个应用
  2. 更轻量:核心库仅 ~1.2kB(gzipped),远小于 Redux + React-Redux 组合
  3. 去中心化 Store:可以创建多个独立的 store,按需组合使用
  4. selector 自动优化:内置基于 Object.is 的渲染优化(选择性订阅)
  5. 架构更纯粹:Redux 强依赖 action/reducer 模式,Zustand 则通过 set 函数与 store 定义合并在一起,API 更接近普通 JavaScript 对象的读写

六、一句话总结

Zustand 的核心原理可以精炼为 三句话

  1. 存储:利用 闭包 中的 let state 变量保存应用状态
  2. 通信:利用 发布-订阅模式 + Set 管理监听器,状态变更时遍历通知所有订阅者
  3. 框架绑定:利用 React 18 的 useSyncExternalStore 安全地将外部 store 接入 React 渲染流程
  4. 扩展:通过 高阶函数(洋葱模型)createStatesetget 进行包装,实现中间件机制

这四种基础机制的组合,造就了 Zustand 这个体积极小、设计优雅的状态管理库。