ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

react - useSyncExternalStore 使用指南

react - useSyncExternalStore 使用指南 文章目录一、 为什么需要 useSyncExternalStore1. 解决并发渲染中的“撕裂Tearing”现象2. 传统 useEffect 模式的痛点二、 API 语法解析三、 实战案例场景 1优雅地订阅浏览器 API如网络状态 navigator.onLine场景 2实现一个简易全局 Store手写简易版 Zustand四、 关键踩坑点死循环与引用一致性❌ 错误示范引起无限死循环✅ 正确示范保持不可变数据引用五、 总结与选型指南useSyncExternalStore是 React 18 引入的一个专门用于订阅外部数据源External Store的 Hook。虽然在日常业务组件开发中它的直接使用频率不如useState或useEffect高但它是各大状态管理库如 Redux, Zustand以及自定义 Browser API 订阅 Hook的底层核心基石。一、 为什么需要 useSyncExternalStore1. 解决并发渲染中的“撕裂Tearing”现象在 React 18 之前同步渲染模式渲染过程是一气呵成的不会被中途打断。而在 React 18 引入并发模式Concurrent React后渲染任务变成了可中断、可恢复的。如果在渲染过程中React 暂停了当前任务去处理更高优先级的用户交互而此时外部数据源非 React state 管理的数据如全局 store 或 window 属性发生了变更[组件 A 渲染] ── 读取外部值 1 │ ├─▶ (React 暂停渲染让出主线程) │ 外部数据源发生了更新值变成了 2 │ [组件 B 渲染] ── 读取外部值 2 最终 UI同一张页面上依赖相同数据的组件 A 和组件 B 展示了不一致的内容 这就是所谓的“UI 撕裂 (Tearing)”。2. 传统useEffect模式的痛点在 React 18 之前我们通常用useStateuseEffect订阅外部数据// ❌ 旧做法在并发模式下可能导致 UI 撕裂或闪烁 function useOnlineStatusOld() { const [isOnline, setIsOnline] useState(navigator.onLine); useEffect(() { const handleStatus () setIsOnline(navigator.onLine); window.addEventListener(online, handleStatus); window.addEventListener(offline, handleStatus); return () { window.removeEventListener(online, handleStatus); window.removeEventListener(offline, handleStatus); }; }, []); return isOnline; }缺陷 1UI 撕裂并发渲染期间无法保证状态读取的原子性。缺陷 2额外重绘/闪烁useEffect是在 DOM 挂载之后异步执行的组件首次渲染先拿到旧值挂载后再触发setState重新渲染容易导致 UI 闪烁。useSyncExternalStore的解决机制它强制以**同步Synchronous**方式读取外部数据快照。如果在渲染过程中外部 store 发生了变动React 会放弃当前的渲染立刻重新使用最新的快照再次发起渲染彻底消除了撕裂与闪烁。二、 API 语法解析const state useSyncExternalStore( subscribe, getSnapshot, getServerSnapshot? );参数类型作用subscribe(callback) unsubscribe注册订阅函数。当外部数据发生变化时调用传入的callback告诉 React“数据变了需要重新渲染”getSnapshot() State获取当前外部数据的快照函数。必须返回不可变数据或同一引用如果数据没变。getServerSnapshot?(可选)() State专为 SSR 服务端渲染提供的初始快照函数避免服务端与客户端 Hydration 不匹配。三、 实战案例场景 1优雅地订阅浏览器 API如网络状态navigator.onLine这是useSyncExternalStore在日常开发中最推荐的用例之一import { useSyncExternalStore } from react; // 1. 定义订阅逻辑 function subscribeOnline(callback: () void) { window.addEventListener(online, callback); window.addEventListener(offline, callback); return () { window.removeEventListener(online, callback); window.removeEventListener(offline, callback); }; } // 2. 定义快照获取函数 function getOnlineSnapshot() { return navigator.onLine; } // 3. 封装为自定义 Hook export function useOnlineStatus() { return useSyncExternalStore( subscribeOnline, getOnlineSnapshot, () true // SSR 场景下的默认兜底值 ); } // 4. 在组件中使用 function NetworkStatusBadge() { const isOnline useOnlineStatus(); return ( div className{isOnline ? bg-green : bg-red} {isOnline ? 网络正常 : ⚠️ 已断网} /div ); }场景 2实现一个简易全局 Store手写简易版 Zustand如果你想实现一个脱离 React 树管理可以在 React 组件外直接store.setState(...)的全局状态useSyncExternalStore是完美工具import { useSyncExternalStore } from react; // 1. 创建外部 Store function createStoreT(initialState: T) { let state initialState; const listeners new Set() void(); return { getState: () state, setState: (fnOrValue: T | ((prev: T) T)) { state typeof fnOrValue function ? (fnOrValue as (prev: T) T)(state) : fnOrValue; // 触发所有订阅者通知 React 更新 listeners.forEach(listener listener()); }, subscribe: (listener: () void) { listeners.add(listener); return () listeners.delete(listener); } }; } // 实例化 Store export const globalCountStore createStore({ count: 0 }); // 2. 在组件中使用 function CounterApp() { // 订阅 Store const state useSyncExternalStore( globalCountStore.subscribe, globalCountStore.getState ); return ( div h3Count: {state.count}/h3 button onClick{() globalCountStore.setState(s ({ count: s.count 1 }))} 加 1 (可从任意地方触发) /button /div ); }四、 关键踩坑点死循环与引用一致性在写getSnapshot时绝对不能每次都返回一个新创建的对象❌ 错误示范引起无限死循环function BadComponent() { const state useSyncExternalStore( store.subscribe, // ❌ 错误每次调用 getSnapshot 都返回一个新的对象引用 // React 会认为数据时刻在变化从而引发无限 Re-render 崩溃 () ({ count: store.getState().count }) ); }✅ 正确示范保持不可变数据引用// ✅ 方式 A只返回基本数据类型Primitive Value const count useSyncExternalStore( store.subscribe, () store.getState().count // 返回 number 基础类型天然支持 Object.is 比较 ); // ✅ 方式 B如果必须返回对象请确保 Store 内部更新时采用不可变Immutable深/浅拷贝 const state useSyncExternalStore( store.subscribe, store.getState // getState 保证在未更新时返回完全相同的对象引用 );五、 总结与选型指南维度useState/useReduceruseSyncExternalStore状态归属React 组件树内部React 状态React 外部如全局 Store, Window API, LocalStorage渲染模式异步并发/批处理渲染强制同步读取快照规避撕裂适用场景组件内部 UI 状态如弹窗开关、表单输入状态库开发、浏览器事件订阅、跨框架全局状态共享核心建议普通业务开发中处理组件自身的 State 依然优先选择useState只有在需要**订阅 React 框架外部的数据源如 window resize、media queries、自定义全局事件通知、封装 Redux/Zustand 这类外部 Store**时才使用useSyncExternalStore。 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目
返回列表