zustand
Zustand 是一个轻量级的 React 状态管理库,其核心原理可以用“三个层、两个核心、一套扩展”来概括。下面从面试高频考点出发,逐步拆解。
一、整体架构:三层设计
Zustand 源码采用分层架构,主要包含三个核心文件:
- vanilla.ts:框架无关的核心状态管理逻辑,可在任何 JavaScript 环境(甚至 Node.js)运行
- react.ts:React 集成层,连接 Zustand store 与 React 组件
- 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 和 -0、NaN 相等)比较新旧状态:
- 无变化:跳过更新,不会触发重渲染(性能优化)
- 有变化:更新
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:
- 调用
createStore创建框架无关的 vanilla store API - 创建
useBoundStore函数,内部调用useStore(基于useSyncExternalStore的封装) - 通过
Object.assign将 store API 方法(setState、getState、subscribe)合并到useBoundStore上 - 返回合并后的函数,既可以当 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 中间件的区别
| 对比维度 | Redux | Zustand |
|---|---|---|
| 中间件形式 | 管道(pipeline) | 增强器(enhancer) |
| 拦截对象 | 拦截 dispatch 的 action | 包装 createState 函数 |
| 扩展方式 | 多个中间件串联成处理链 | 高阶函数层层嵌套(洋葱模型) |
| 核心原理 | next(action) 链式传递 | 对 set、get、api 进行包装改造 |
Redux 中间件像一个管道,一个 action 流入,经过一系列中间件处理后,最终到达 reducer;Zustand 中间件则像在 create 函数外面一层层套壳,每一层都能对 set 或 get 进行增强。
核心模式:包装 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 的中间件通过对 set、get、api 进行拦截和包装,实现了日志记录、持久化、Immer 可变更新等一系列功能,而 Zustand 核心代码对此浑然不知——这就是函数组合和控制反转的设计之美。这种设计使得中间件之间可以任意组合,形成“洋葱模型”的执行顺序:从外向内穿透经过层层中间件,到达核心 store 后,响应再从内向外返回。
五、与 Redux 的核心区别(面试加分点)
- 无需 Provider:Zustand 不需要用 Context Provider 包裹整个应用
- 更轻量:核心库仅 ~1.2kB(gzipped),远小于 Redux + React-Redux 组合
- 去中心化 Store:可以创建多个独立的 store,按需组合使用
- selector 自动优化:内置基于
Object.is的渲染优化(选择性订阅) - 架构更纯粹:Redux 强依赖 action/reducer 模式,Zustand 则通过
set函数与 store 定义合并在一起,API 更接近普通 JavaScript 对象的读写
六、一句话总结
Zustand 的核心原理可以精炼为 三句话:
- 存储:利用 闭包 中的
let state变量保存应用状态 - 通信:利用 发布-订阅模式 +
Set管理监听器,状态变更时遍历通知所有订阅者 - 框架绑定:利用 React 18 的
useSyncExternalStore安全地将外部 store 接入 React 渲染流程 - 扩展:通过 高阶函数(洋葱模型) 对
createState、set、get进行包装,实现中间件机制
这四种基础机制的组合,造就了 Zustand 这个体积极小、设计优雅的状态管理库。