Skip to content
目录

并发渲染和僵尸子组件问题

React 18 并发渲染可打断、恢复 render。外部 store 若订阅方式不当,会出现 tearing(撕裂)zombie child(僵尸子组件)。Zustand v4+ 依赖 useSyncExternalStore 规避这两类问题。


1. 并发下两类经典问题

1.1 Tearing(撕裂)

同一次「逻辑渲染」中,组件 A 与 B 通过各自闭包读到 不同版本的 store,UI 互不一致。

典型诱因useState + useEffect(subscribe) 在 render 与 effect 之间 store 已变,但子组件仍用旧 snapshot。

1.2 Zombie Child(僵尸子组件)

父更新导致子组件已从树中移除,但此前排队的订阅回调仍执行,回调里使用卸载时的 props/闭包 state,与当前树不一致。

列表项删除、路由切换、条件渲染关闭分支时最常见。


2. useSyncExternalStore 如何解决

能力作用
渲染期 getSnapshot同一 render _pass 内快照一致
Object.is 比较快照未变不触发重渲染
与 React 调度同步的 subscribe卸载后不再对已卸载组件触发 setState
getServerSnapshotSSR 首帧一致

Zustand 的 useStore 把 store 的 subscribe / getState / getInitialState 接到上述 API,无需业务代码处理并发补丁


3. 使用侧仍要注意

场景建议
列表子项订阅 items.find(id)父删项后子卸载;避免在已卸载组件的 effect 里依赖过期 item
选择器返回新对象useShallow,避免额外重渲染与边界竞态
在 render 里 getState() 写 store反模式;写在事件/ effect
SSR提供与水合一致的初始 state;persist 注意 hydration mismatch

4. 与 React Redux 文档的关系

「Stale props and zombie children」最初在 react-redux 语境下讨论。Zustand 无 connect 层,但外部 store + 错误订阅的并发问题同构;解法统一到 useSyncExternalStore


5. 和 React 文档的衔接


6. 总结

并发安全不是 Zustand 在 reconciler 里做了特殊 case,而是用对接 React 官方外部 store 协议保证快照一致、订阅生命周期正确。选型上应用 Zustand 4+ 且 React 18+;勿自写 useState 订阅 store。

MIT License.