我用 100 行代码搞定 React 状态管理,同事:这不可能!

背景

两年前,我写了一个 200 行的 React 状态管理库,叫做 XSta

当时我写这个库的初衷是,我需要一个非常轻量级的状态管理库,能够满足我的基本需求,同时又不会引入太多的学习成本。

XSta 成功了:

  • 它提供了与 useState 一致的 API,零学习成本
  • 同时又非常轻量级,核心代码仅 200 行(包含注释和类型定义)

实现

但是我觉得它还不够好,至少还可以再简单一点。

所以,这一次我准备用 100 行代码重写一个状态管理库,叫做 XStore(也是 ZenBox 的前身)。

tsx 复制代码
import { useCallback, useEffect, useRef, useState } from "react";
import { shallowEqual } from "@del-wang/equals";

interface Listener<T, V = T> {
  prev: V;
  select: (state: T) => V;
  equal: (a: V, b: V) => boolean;
  onChange: (current: V, prev: V) => void;
}

const identity = <T>(state: T) => state;

export class XStore<T extends Record<string, any>> {
  constructor(private _state: T) {}
  private _listeners: Set<Listener<T, any>> = new Set();
  get value(): Readonly<T> {
    return this._state;
  }
  setState = (newState: T | ((prev: Readonly<T>) => T)) => {
    this._state =
      typeof newState === "function" ? newState(this._state) : newState;
    for (const listener of this._listeners) {
      const current = listener.select(this._state);
      if (listener.equal(listener.prev, current)) continue; // no changes
      listener.onChange(current, listener.prev);
      listener.prev = current;
    }
  };
  subscribe = <V = T>(options: {
    onChange: (current: V, prev: V) => void;
    select?: (state: T) => V;
    equal?: (a: V, b: V) => boolean;
  }) => {
    const { onChange, select = identity, equal = shallowEqual } = options;
    const listener = { onChange, select, equal, prev: select(this._state) };
    this._listeners.add(listener);
    return () => this._listeners.delete(listener);
  };
}

export function useWatch<
  const S extends XStore<any>[],
  V = { [K in keyof S]: S[K]["value"] }
>(options: {
  stores: S;
  select?: (...states: { [K in keyof S]: S[K]["value"] }) => V;
  onChange: (current: V, prev: V) => void | VoidFunction;
  equal?: (a: V, b: V) => boolean;
}): V {
  const { stores, onChange, select = identity, equal = shallowEqual } = options;

  const refs = useRef({
    cleanup: null as null | VoidFunction,
    prev: select(...stores.map((store) => store.value)),
    ...{ stores, onChange, select, equal },
  });
  refs.current = { ...refs.current, stores, onChange, select, equal };

  const check = useCallback(() => {
    const { prev, select, equal, onChange, cleanup, stores } = refs.current;
    const current = select(...stores.map((store) => store.value));
    if (equal(prev, current)) return; // no changes
    cleanup?.(); // cleanup previous effect
    refs.current.cleanup = onChange(current, prev) || null;
    refs.current.prev = current;
  }, []);

  check();

  useEffect(() => {
    const unsubscribes = stores.map((store) =>
      store.subscribe({ onChange: check })
    );
    return () => {
      refs.current.cleanup?.();
      unsubscribes.forEach((unsubscribe) => unsubscribe());
    };
  }, [...stores]);

  return refs.current.prev;
}

export function useComputed<
  const S extends XStore<any>[],
  V = { [K in keyof S]: S[K]["value"] }
>(options: {
  stores: S;
  select?: (...states: { [K in keyof S]: S[K]["value"] }) => V;
  equal?: (a: V, b: V) => boolean;
}) {
  const { stores, select, equal } = options;
  const [_, setState] = useState({});
  const rebuild = useCallback(() => setState({}), []);
  return useWatch({ stores, select, equal, onChange: rebuild });
}

用法

麻雀虽小,五脏俱全。XStore 只用 100 行代码就实现了 React 状态管理的核心功能:

tsx 复制代码
// 新建一个 store,自动类型推断,无需手写 interface
const counterStore = new XStore({ count: 0 });

const Counter = () => {
  // 当状态更新时,会自动触发组件 re-render
  const doubleCount = useComputed({
    stores: [counterStore],
    select: (state) => state.count * 2,
  });

  // 像 Vue 里的 watch 一样监听 store 状态变化
  useWatch({
    stores: [counterStore],
    onChange: (current, prev) => {
      console.log("count changed", current, prev);
      return () => {
        console.log("cleanup effect");
      };
    },
  });

  const increment = () => {
    // 获取当前状态
    const count = counterStore.value.count;
    // 更新状态
    counterStore.setState({ count: count + 1 });
  };

  return (
    <div>
      <p>Double Count: {doubleCount}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

可以看到,XStore 已经很接近 ZenBox 了。

后续

后来,我在 XStore 的基础上创造了 ZenBox,添加了更多功能,比如:

  • 更精简、符合直觉的 API 设计
  • 更完善的 TypeScript 类型自动推断
  • 支持局部或使用 Immer 的方式更新 State
  • Vue 风格的开发体验: useComputed, useWatch, useWatchEffect
  • ......

现在,ZenBox 已经成为了我新项目的首选 React 状态管理库(之前是 Zustand)。

相关推荐
Java陈序员4 小时前
轻量运维面板!一款现代化的服务器控制面板工具!
运维·服务器·python·react.js·github
光影少年7 小时前
RN首屏加载速度优化全方案
react native·react.js·掘金·金石计划
前端精髓9 小时前
React 更新 state 中的对象
javascript·react.js·ecmascript
10share15 小时前
我为什么用 React 重写了一个 VitePress
前端·react.js
天道kabuto15 小时前
前端每日知识点:React 与 Vue Diff 算法对比 · 完整总结
vue.js·react.js·前端框架
moMo16 小时前
React 全栈实战:从组件到鉴权的完整指南
react.js
光影少年1 天前
如何实现RN 多环境、多渠道打包
前端·react native·react.js
云上工程笔记2 天前
AstraFlow + React/Vite:如何用自然语言开发并部署一个 AI 咖啡网站原型
前端·人工智能·react.js
光影少年2 天前
React18 对RN 的影响
android·前端·react.js·ios·前端框架