跳转至

Mini State Store

从 569 行的 src/state/AppStateStore.ts 提炼到 ~60 行useSyncExternalStore 模式。 5 个测试场景:基本 / 订阅 / Selector / 同 ref 不通知 / React 集成。

文件

mini-state-store/
├── src/
│   ├── store.ts  Store 类 + createSyncExternalStore polyfill(~60 行)
│   └── cli.ts    5 测试场景(~100 行)
└── README.md

npm install && npm start

真实代码对照

Demo 真实文件 简化
Store AppStateStore.ts:30 (~200 行) 60 行
subscribeWithSelector AppStateStore.ts:400 (~150 行) 8 行(带 equality)
createSyncExternalStore React 18 useSyncExternalStore polyfill 3 方法

核心 4 件套

1️⃣ 三件套 API

class Store<T> {
  getState(): T              // 读
  setState(next | (s)=>next) // 写
  subscribe(listener): ()=>void  // 订阅(返 unsubscribe)
}

2️⃣ Same-ref 不通知

setState(next) {
  if (next === this.state) return;  // ← 关键优化
  this.state = next;
  this.listeners.forEach((l) => l(this.state));
}
重要:传新对象就算内容一样也通知;传同一个 ref 就不通知。用 selector 比较 解决"对象新但内容同"的场景。

3️⃣ Selector 订阅(精细粒度)

subscribeWithSelector(
  selector: (state) => U,        // 选哪部分
  listener: (selected, prev) => void,
  equalityFn: Object.is,         // 默认 Object.is
)
避免不必要 re-render:选 state.user.name 时,state.user.age 变了不通知。

4️⃣ React useSyncExternalStore 集成

React 18 提供的 hook,专门订阅外部 store。本 demo 的 createSyncExternalStore 返回 3 个方法: - getSnapshot() —— 同步取当前 state(React 在 render 时调) - subscribe(onStoreChange) —— 订阅 + 通知 React - getServerSnapshot() —— SSR 用,避免 hydration mismatch

测试输出

📌 Test 1: Basic getState / setState
  initial: {"count":0,"user":null,"theme":"light"}
📌 Test 2: subscribe() listener
  [listener] value changed: 1
  [listener] value changed: 2
📌 Test 3: subscribeWithSelector() with equality
  (age changed but name same: NOT notified)
  [selector] name: Alice → Bob
📌 Test 4: setState same ref = no notify
  notified 2 times
📌 Test 5: useSyncExternalStore integration
  React would re-render 2 times

进阶练习

  1. 加 middleware:logger / devtools / persistence / undo
  2. 加 immer:自动深拷贝 + 结构共享
  3. 加 time-travel debug:记录所有 state 历史,支持回放
  4. 加 batch updates:用 React 18 的 unstable_batchedUpdates 合并多次 setState
  5. 加 async action:dispatch(asyncFn) 支持 thunk
  6. 加 selector memoization:用 reselect 缓存 selector 结果

相关阅读