React 集成机制
React 层(src/react.ts)把 Vanilla Store 接到组件树:useSyncExternalStore + 选择器。这是 Zustand 在 React 18 并发下避免 tearing 与僵尸子组件问题的关键。
1. 为什么不用 useState + useEffect 订阅
旧模式:mount 时 getState + subscribe 里 setState。
| 问题 | 表现 |
|---|---|
| Tearing | 同一次渲染中不同组件读到不同版本的 store |
| 僵尸子组件 | 已卸载组件的订阅回调仍跑,闭包里是过期 props/state |
| SSR 不一致 | 服务端与客户端首帧快照可能对不齐 |
React 18 的 useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?) 为「外部 store」规定了订阅与快照协议,Zustand 直接复用。
2. useStore 做了什么
概念上三步:
subscribe:用 store 的subscribe,由 React 管理挂载/卸载。getSnapshot:selector(getState());React 用Object.is比较连续两次快照,相同则不重渲染。getServerSnapshot(可选):selector(getInitialState()),SSR 首帧与客户端水合对齐。
create() 返回的 Hook 本质是 useStore(storeApi, selector);默认 selector 为整棵 state(不推荐在生产大 store 上使用)。
选择器应用 useCallback 稳定引用:selector 引用变会导致 getSnapshot 依赖变,可能多余重订阅(eslint 常提示)。
3. 与 Context 的对比
| Context | Zustand + useSyncExternalStore | |
|---|---|---|
| 更新传播 | Provider value 变 → 消费者常全刷 | 仅 selector 切片变才刷 |
| 样板 | Provider 包裹 | 无 Provider |
| 测试注入 | 换 Provider value | 需自建 Context 包 store 或 mock 模块 |
4. create 与 Store 实例
- 每个
create(...)产生独立 store 单例(模块级 const)。 - 同一文件导出
useBearStore即全局共享;多实例需多次create或 Vanilla + Context 注入。
5. 并发相关(延伸阅读)
- Tearing / 僵尸子组件:见 06-并发渲染。
- React 侧原理:04-Hooks useSyncExternalStore。
6. 总结
React 集成层极薄:不重新发明订阅,而是把现有 subscribe/getState 交给 React 官方外部 store Hook。性能与正确性主要取决于 选择器粒度 与 快照引用稳定(07-性能优化)。