如何用 30 行代码手写一个支持选择器(Selector)与细粒度重渲染控制的 Zustand 状态库?
Zustand 的源码极其精炼,其核心架构由两部分组成:1. 与框架无关的 Vanilla 发布订阅状态容器(createStore);2. 基于 React 18 原生 useSyncExternalStore 桥接并支持 selector 切片比对的 React Hook(create)。
一、 完整手写实现(含 Vanilla 核心 + React Hook 桥接 + 浅比较优化)
import { useSyncExternalStore } from 'react'
// 第一层:与 React 完全解耦的纯 JS 发布订阅状态机(可在任意 JS/TS 工具函数中直接调用)
export function createStore(createState) {
let state
const listeners = new Set()
const getState = () => state
const setState = (partial, replace = false) => {
const nextState = typeof partial === 'function' ? partial(state) : partial
// 使用 Object.is 拦截相同引用更新
if (!Object.is(nextState, state)) {
const previousState = state
state = replace ? nextState : Object.assign({}, state, nextState)
// 通知所有订阅者并传入新旧状态
listeners.forEach((listener) => listener(state, previousState))
}
}
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener) // 返回优雅退订函数
}
// 初始化状态:将 setState、getState 注入给用户的定义函数
state = createState(setState, getState)
return { getState, setState, subscribe }
}
// 第二层:桥接 React 并发渲染安全原语 useSyncExternalStore
const identity = (s) => s
export function create(createState) {
const api = createStore(createState)
const useBoundStore = (selector = identity) => {
return useSyncExternalStore(
api.subscribe,
() => selector(api.getState()), // 客户端读取切片快照(React 内部自动用 Object.is 比对切片返回值决定是否重渲染!)
() => selector(api.getState()) // SSR 服务端渲染读取初始快照
)
}
// 将 getState / setState / subscribe 静态方法直接挂载到 Hook 函数上,支持组件外调用
Object.assign(useBoundStore, api)
return useBoundStore
}二、 面试深挖:当 Selector 返回新对象时为什么会无限重渲染?如何用 useShallow 解决?
在 React 18+ 中,useSyncExternalStore 会对 getSnapshot(即 () => selector(api.getState()))的返回值做严格的 Object.is(prevSlice, nextSlice) 比对。如果用户写成:const { a, b } = useStore(s => ({ a: s.a, b: s.b }))
由于每次调用 selector 都返回一个全新的对象字面量引用 {},Object.is 永远为 false,会立即触发 React 报错 Maximum update depth exceeded!
// 手写 Zustand 必备伴侣:useShallow 浅比较包装器(当新旧对象第一层属性值未变时复用上一次的对象引用)
import { useRef } from 'react'
export function useShallow(selector) {
const prev = useRef()
return (state) => {
const next = selector(state)
if (prev.current && shallowEqual(prev.current, next)) {
return prev.current // 关键:属性浅相等时返回上一次缓存的旧引用,让 useSyncExternalStore 的 Object.is 通过!
}
prev.current = next
return next
}
}