在 React 面试中,状态管理几乎是必问的一个点。
很多人会说 Redux、Zustand、MobX,但一旦被问:
“zustand 为什么这么轻?它是怎么做到按需更新的?”
就容易卡住。
这篇文章,我们手写一个简易版 zustand,带你彻底搞懂它的核心设计。
一、为什么需要 Zustand?
先从本质讲清楚,不然源码是死的。
在 React 中,我们常见问题是:
1. 组件通信复杂
A -> B -> C -> D 传 props
层级一深:
- props drilling(层层传递)
- 维护成本高
- 可读性差
2. 状态无法统一管理
比如:
- 用户信息
- 登录状态
- 主题配置
这些都是全局共享状态
3. Redux 太重
Redux 的问题:
- action
- reducer
- dispatch
- store
学习成本高,样板代码多
Zustand 的优势:
- API 极简
- 无 Provider
- 支持按需更新(性能好)
- 代码量少
一句话总结:
Zustand = 一个“带订阅能力的全局 store + React Hook”
二、核心设计思想
Zustand 本质只有三件事:
1. 存状态(store)
state = {
count: 0
}
2. 改状态(setState)
setState({ count: 1 })
3. 响应更新(subscribe)
listeners.forEach(fn => fn())
核心模式:发布-订阅
可以类比:
- 抖音博主(store)
- 粉丝(组件)
- 发布视频(state 更新)
一旦更新:
所有订阅者收到通知
三、实现一个最小 Store
先不管 React,写一个纯 JS 版本。
1. createStore
const createStore = (createState) => {
let state
const listeners = new Set()
const getState = () => state
const setState = (partial, replace = false) => {
const nextState =
typeof partial === 'function' ? partial(state) : partial
if (!Object.is(nextState, state)) {
const previousState = state
state = replace
? nextState
: typeof nextState !== 'object' || nextState === null
? nextState
: Object.assign({}, state, nextState)
listeners.forEach(listener => listener(state, previousState))
}
}
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
const destroy = () => {
listeners.clear()
}
const api = {
setState,
getState,
subscribe,
destroy
}
state = createState(setState, getState, api)
return api
}
面试重点讲解
1. 为什么用 Set?
const listeners = new Set()
优点:
- 自动去重
- 删除方便
- 性能好
2. 为什么支持函数式 setState?
set((state) => ({ count: state.count + 1 }))
原因:
避免闭包拿到旧状态(类似 React setState)
3. 为什么要 Object.assign?
Object.assign({}, state, nextState)
实现“局部更新”
四、接入 React:实现 useStore
现在 store 有了,但 React 还用不了。
我们需要一个 Hook:
useStore
import { useEffect, useState } from 'react'
const useStore = (api, selector) => {
const [, forceRender] = useState(0)
useEffect(() => {
const unsubscribe = api.subscribe((state, prevState) => {
const newSelected = selector(state)
const oldSelected = selector(prevState)
if (newSelected !== oldSelected) {
forceRender(Math.random())
}
})
return unsubscribe
}, [])
return selector(api.getState())
}
面试重点
1. selector 是核心!
const count = useStore(store, state => state.count)
只订阅你需要的部分
2. 为什么要对比新旧值?
if (newSelected !== oldSelected)
避免无意义渲染(zustand 性能核心)
3. 为什么强制渲染?
forceRender(Math.random())
React 没有“手动刷新”,只能:
改 state 触发更新
五、实现 create(Zustand 精髓)
Zustand 最经典的 API:
const useStore = create(...)
我们来实现:
export const create = (createState) => {
const api = createStore(createState)
const useBoundStore = (selector = state => state) => {
return useStore(api, selector)
}
Object.assign(useBoundStore, api)
return useBoundStore
}
这里是灵魂设计(面试必讲)
1. Hook + Store 合体
useCounterStore()
useCounterStore.setState()
一个函数,两种能力:
- Hook(组件用)
- API(外部用)
2. Object.assign 的作用
Object.assign(useBoundStore, api)
把 store 方法挂到 hook 上
六、完整使用示例
创建 Store
const useCounterStore = create((set, get) => ({
count: 0,
text: '初始文本',
increment: () => set(state => ({ count: state.count + 1 })),
updateText: (text) => set({ text })
}))
组件 A:只订阅 count
const count = useCounterStore(state => state.count)
text 变化不会触发渲染
组件 B:只订阅 text
const text = useCounterStore(state => state.text)
count 变化不会影响它
组件 C:直接操作 API
useCounterStore.setState({
count: 100
})
七、Zustand 为什么性能好?
核心就一句话:
按需订阅 + 精准更新
对比 Redux
Redux:
state 变了 → 所有 connect 组件可能更新
Zustand:
只更新 selector 命中的组件
本质原因
selector(state)
每个组件只关心一小部分数据
八、你可以在面试中这样说
如果面试官问:
Q:zustand 原理是什么?
你可以这样答:
第一层(基础版)
zustand 本质是一个基于发布订阅的状态管理库,通过 store 存储状态,通过 subscribe 监听变化,然后用 hook 绑定 React,实现响应式更新。
第二层(进阶版)
它的核心优化在于 selector,组件只订阅自己关心的状态,通过新旧值对比来避免不必要渲染。
第三层(源码级)
内部通过 createStore 创建 store,维护 listeners 集合,setState 时触发订阅,useStore 通过 useEffect 自动订阅,实现 React 组件更新。
九、总结
我们手写 zustand,其实只做了 4 件事:
1. 存状态
let state
2. 改状态
setState()
3. 订阅更新
subscribe()
4. 绑定 React
useStore()
最后一句话总结
Zustand 并不神秘,它只是把“发布订阅 + Hook”做到了极致简洁。