阅读视图

发现新文章,点击刷新页面。

手写一个精简版 Zustand:深入理解 React 状态管理的核心原理

“读源码不是为了造轮子,而是为了更好地驾驭轮子。”
本文将带你从零实现一个功能完整、结构清晰的 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 是用户传入的初始化函数,接收 setget
  • 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());
};

💡 为什么有效?

  • CountDisplayselector: 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 为何如此优秀?

  1. 去中心化设计
    无需 Provider 嵌套,状态即模块,天然支持代码分割。
  2. 响应式粒度控制
    通过 selector 实现状态切片订阅,比 Context + useReducer 更高效。
  3. 函数式 + 响应式融合
    set 接收函数支持状态派生,get 支持跨字段计算,灵活又安全。
  4. 极致简洁
    核心代码不足 100 行,却覆盖 90% 场景,体现“少即是多”的哲学。

📌 总结

通过手写 Zustand,我们不仅掌握了:

  • 发布-订阅模式在状态管理中的应用;
  • React 自定义 Hook 与状态同步的技巧;
  • 如何实现精准渲染以提升性能;

更重要的是,理解了优秀库的设计思想简单、专注、可组合

“当你能手写一个库,你就真正拥有了它。”

下次面试被问到 Zustand 原理时,不妨自信地说:
“我不仅用过,我还写过。”

❌