手写一个精简版 Zustand:深入理解 React 状态管理的核心原理
2026年3月24日 16:47
“读源码不是为了造轮子,而是为了更好地驾驭轮子。”
本文将带你从零实现一个功能完整、结构清晰的 Zustand 精简版,并深入剖析其设计哲学与性能优化秘诀。
🌟 为什么是 Zustand?
在 React 生态中,状态管理方案层出不穷。Redux 曾长期占据主流,但其样板代码多、学习曲线陡峭的问题饱受诟病。而 Zustand 凭借极简 API、零模板、自动优化渲染等特性,迅速成为开发者的新宠(GitHub ⭐ 超 30k)。
它的核心优势在于:
-
无需 Provider,直接
import使用; - 天然支持按需订阅,避免无效重渲染;
-
API 极简,一个
create搞定一切; - 轻量(仅 ~1KB),无依赖。
但你是否想过:Zustand 是如何做到这一切的?
今天,我们就来手写一个精简版 Zustand,揭开它高性能、易用背后的秘密。
🔧 第一步:构建最基础的状态容器
状态管理的核心无非三件事:存、取、改。
我们先实现一个最简 Store:
const createStore = (createState) => {
let state;
const listeners = new Set();
// 获取当前状态
const getState = () => state;
// 修改状态
const setState = (partial) => {
const nextState = typeof partial === 'function' ? partial(state) : partial;
state = Object.assign({}, state, nextState);
// 通知所有监听者
listeners.forEach(listener => listener());
};
// 订阅状态变化
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener); // 返回取消订阅函数
};
// 初始化状态
state = createState(setState, getState);
return { getState, setState, subscribe };
};
✅ 关键点解析:
-
createState是用户传入的初始化函数,接收set和get; -
setState支持传入对象或函数(类似 React 的useState); - 使用
Set存储监听器,避免重复订阅; - 状态变更后,通知所有订阅者 —— 这就是“发布-订阅”模式。
🎣 第二步:让 React 组件能“感知”状态变化
光有 Store 不够,React 组件需要在状态变化时自动重渲染。这就需要一个自定义 Hook。
import { useState, useEffect } from 'react';
const useStore = (api, selector = (state) => state) => {
const [, forceUpdate] = useState(0);
useEffect(() => {
const unsubscribe = api.subscribe(() => {
forceUpdate(Math.random()); // 强制更新组件
});
return unsubscribe;
}, []);
return selector(api.getState());
};
⚠️ 问题来了:这个实现会导致所有使用该 Store 的组件在任意状态变化时都重渲染!这显然违背了 Zustand 的“按需更新”原则。
🚀 第三步:实现“精准订阅”——只在关心的状态变化时更新
Zustand 的核心性能优势在于:组件只订阅自己需要的状态片段。
改进思路:
- 比较
selector前后的值; - 只有当选中的值发生变化时,才触发重渲染。
const useStore = (api, selector) => {
const [, forceUpdate] = useState(0);
useEffect(() => {
const unsubscribe = api.subscribe((newState, oldState) => {
const newSelected = selector(newState);
const oldSelected = selector(oldState);
// 使用 Object.is 进行严格相等比较(处理 NaN、-0 等边界)
if (!Object.is(newSelected, oldSelected)) {
forceUpdate(Math.random());
}
});
return unsubscribe;
}, [selector]); // 注意:selector 应为稳定函数(通常用 useCallback 包裹)
return selector(api.getState());
};
💡 为什么有效?
-
CountDisplay只selector: state => state.count; - 当
text变化时,count未变 →newSelected === oldSelected→ 不重渲染; - 完美实现细粒度更新!
🏗️ 第四步:封装 create 高阶函数,提供开发者友好的 API
Zustand 的魔法入口是 create。它返回一个 既是 Hook 又是 Store API 对象 的函数。
export const create = (createState) => {
const api = createStore(createState);
const useBoundStore = (selector) => {
return useStore(api, selector);
};
// 将 Store 的方法(setState, getState 等)挂载到 Hook 上
Object.assign(useBoundStore, api);
return useBoundStore;
};
✨ 这样设计的好处:
- 在组件中:
const count = useStore(state => state.count); - 在非组件中(如工具函数、事件回调):
useStore.setState({ count: 10 }); - 一套 API,两种用法,无缝切换。
🧪 完整 Demo:验证局部更新效果
const useCounterStore = create((set) => ({
count: 0,
text: '初始文本',
increment: () => set(state => ({ count: state.count + 1 })),
updateText: (text) => set({ text })
}));
// 只订阅 count
const CountDisplay = () => {
console.log('CountDisplay 渲染了');
const count = useCounterStore(state => state.count);
const increment = useCounterStore(state => state.increment);
return <div>Count: {count} <button onClick={increment}>+</button></div>;
};
// 只订阅 text
const TextDisplay = () => {
console.log('TextDisplay 渲染了');
const text = useCounterStore(state => state.text);
const updateText = useCounterStore(state => state.updateText);
return <input value={text} onChange={e => updateText(e.target.value)} />;
};
✅ 打开控制台你会发现:
- 点击 “+” 按钮 → 只有
CountDisplay重新渲染; - 修改输入框 → 只有
TextDisplay重新渲染; - 完美隔离,性能拉满!
💡 深度思考:Zustand 为何如此优秀?
-
去中心化设计
无需Provider嵌套,状态即模块,天然支持代码分割。 -
响应式粒度控制
通过selector实现状态切片订阅,比 Context + useReducer 更高效。 -
函数式 + 响应式融合
set接收函数支持状态派生,get支持跨字段计算,灵活又安全。 -
极致简洁
核心代码不足 100 行,却覆盖 90% 场景,体现“少即是多”的哲学。
📌 总结
通过手写 Zustand,我们不仅掌握了:
- 发布-订阅模式在状态管理中的应用;
- React 自定义 Hook 与状态同步的技巧;
- 如何实现精准渲染以提升性能;
更重要的是,理解了优秀库的设计思想:简单、专注、可组合。
“当你能手写一个库,你就真正拥有了它。”
下次面试被问到 Zustand 原理时,不妨自信地说:
“我不仅用过,我还写过。”