zustand 源码分析
前置阅读:
zustand 简介
zustand(德语”状态”的意思)是一个轻量级、快速且可扩展的状态管理库。它基于简化的 Flux 原则,提供了基于 Hook 的简洁 API,代码量极小(压缩后不到 1KB),同时避免了传统状态管理方案的复杂性。
核心特点:
- 无需 Provider:不需要用 Context Provider 包裹应用,避免了 Context 带来的性能问题和渲染优化难题
- 基于 React.useSyncExternalStore:使用 React 18+ 官方 API 实现状态订阅,正确处理了并发渲染、zombie child 等常见问题
- 简洁的 API:通过
create函数创建 store,返回的 hook 可以直接使用,支持 selector 精确订阅状态切片 - 灵活的扩展能力:支持中间件模式,可以轻松集成 devtools、persist、immer 等插件
- TypeScript 友好:完整的类型推导支持,开发体验优秀
相比 Redux,zustand 减少了大量模板代码;相比 Context API,它提供了更细粒度的更新控制和更好的性能表现。
zustand 基础用法
使用 zustand 非常简单:通过 create 函数创建一个 store hook。create 接收一个状态创建函数,该函数接收 set、getState 和 api 三个参数,返回初始状态对象和更新方法。
创建好的 hook 可以直接在组件中使用,无需任何 Provider 包裹。hook 支持传入一个可选的 selector 函数来选择状态中的特定部分,这样可以实现精确订阅:只有当 selector 选中的状态发生变化时,组件才会重新渲染,避免了不必要的性能开销。如果不传 selector,则返回整个状态对象(注意这会导致状态任何变化都会触发重渲染)。
import { create } from 'zustand'
const useBear = create((set) => ({
bears: 0,
increase: () => set((state) => ({bears: state.bears + 1}))
}))
const App = () => {
const bears = useBear((state) => state.bears)
return <div>{bears}</div>
}
zustand 的 create 方法执行过程
在前面的例子中,create 接收了一个箭头函数,该函数接收一个名为 set 的参数,调用 set 可以触发状态的变更。
const create = (createState) => createState ? createImpl(createState) : createImpl
当调用 create 并传入状态创建函数时,会执行 createImpl(createState)。createImpl 的实现如下:
const createImpl = (createState) => {
const api = createStore(createState)
const useBoundStore = (selector) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
createImpl 首先调用 createStore 创建 store API,然后创建一个 useBoundStore 函数,该函数可以接收一个可选的 selector 参数。最后通过 Object.assign 将 store API 的方法(setState、getState、subscribe、getInitialState)都挂载到 useBoundStore 上,这样我们就可以直接通过 hook 访问这些方法了。
createStore 的实现
createImpl 内部会调用 createStore 来创建 store。createStore 的核心实现是 createStoreImpl,它负责创建状态存储的核心逻辑:
function createStoreImpl(createState) {
let state;
const listeners = new Set()
// 用于触发状态更新
const setState = (partial, replace) => {
const nextState = typeof partial === 'function'
? partial(state)
: partial
if (!Object.is(nextState, state)) {
const previousState = state
state = replace ?? (typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState)
listeners.forEach((listener) => listener(state, previousState))
}
}
const getState = () => state
const subscribe = (listener) => {
listeners.add(listener)
// Unsubscribe
return () => listeners.delete(listener)
}
const api = {setState, getState, subscribe}
const initialState = (state = createState(setState, getState, api))
const getInitialState = () => initialState
return { setState, getState, subscribe, getInitialState }
}
在 createStoreImpl 执行时,createState 函数会被调用,传入 setState、getState 和 api 作为参数。createState 函数会返回初始状态对象,这个对象会被赋值给 state 变量,同时也被保存为 initialState。代换后,代码如下:
// createState 被调用,返回初始状态
const initialState = (state = createState(setState, getState, api))
// 等价于:
const initialState = (state = {
bears: 0,
increase: () => setState((state) => ({bears: state.bears + 1}))
})
// 此时 state 和 initialState 都指向同一个对象
接下来需要将状态和 React 连接起来,zustand 采用了 React.useSyncExternalStore 来实现这一功能。
在 useStore 函数中,会调用 React.useSyncExternalStore:
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
)
create 创建的 hook 可以接收一个 selector 函数,用来从 state 中选择需要的切片。这个 selector 函数会被应用到 api.getState() 和 api.getInitialState() 的返回值上,从而只订阅状态中相关的部分。
接下来我们看看更新操作是如何触发的。当我们调用 increase 方法时,会执行 set((state) => ({bears: state.bears + 1}))。
这里的 set 函数实际上是 setState。在 setState 中,会计算新的状态,如果状态发生了变化(通过 Object.is 比较),就会遍历所有的 listeners 并调用它们,通知订阅者状态已更新。由于 React.useSyncExternalStore 订阅了 api.subscribe,当 listeners 被触发时,React 就会重新渲染组件,从而更新 UI。
总结
通过分析 zustand 的源码,我们可以看到它之所以能够成为 React 生态中备受欢迎的状态管理方案,主要得益于以下几个方面的设计:
核心架构设计
-
发布-订阅模式:zustand 的核心是基于发布-订阅模式的状态管理。
createStoreImpl维护了一个listenersSet,当状态更新时,会通知所有订阅者。这种设计简单高效,避免了复杂的中间层。 -
React 官方 API 集成:使用
React.useSyncExternalStore作为 React 和外部状态存储之间的桥梁,这是 React 18+ 官方推荐的方式。它不仅解决了并发渲染的问题,还正确处理了 zombie child、context loss 等边界情况。 -
精确的状态订阅:通过 selector 函数,组件可以只订阅状态中需要的部分。配合
Object.is进行严格相等比较,确保只有在相关状态真正变化时才触发重渲染,实现了细粒度的性能优化。
关键实现细节
-
状态更新机制:
setState支持函数式和对象式两种更新方式,自动处理对象合并(浅合并),同时支持replace参数进行完全替换。 -
无 Provider 设计:通过
Object.assign将 store API 的方法挂载到 hook 上,使得 hook 既是函数又是对象,可以直接访问setState、getState等方法,无需通过 Context 传递。 -
初始状态处理:通过
getInitialState保存初始状态快照,配合useSyncExternalStore的第三个参数,确保 SSR 和 hydration 场景下的状态一致性。
zustand 的成功在于它在简洁性和功能性之间找到了完美的平衡。它没有过度设计,而是专注于解决状态管理的核心问题,通过巧妙的设计和 React 官方 API 的配合,实现了既简单又强大的状态管理方案。