
什么时候用它
React 之外有一份会自己变化的数据:浏览器 API、第三方状态库、事件总线。你想在组件里读它,还希望它变了界面跟着更新,就用这个 Hook。
官方有一条注记:能放进 useState 的状态就放进去。这个 API 主要是给「对接已有的非 React 代码」用的。
两个函数的分工
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)subscribe(callback) 负责订阅,数据变化时调用 callback,并返回一个取消订阅的函数。
getSnapshot() 负责读当前值。数据没变时,它必须返回同一个值,React 用 Object.is 比较。
getServerSnapshot 是服务端渲染时用的快照,可选。组件要在服务端渲染却不给这个参数,会直接报错。
一个能跑的例子
import { useSyncExternalStore } from 'react'
function subscribe(callback) { window.addEventListener('online', callback) window.addEventListener('offline', callback) return () => { window.removeEventListener('online', callback) window.removeEventListener('offline', callback) }}
function getSnapshot() { return navigator.onLine}
export function useOnlineStatus() { return useSyncExternalStore(subscribe, getSnapshot)}两个函数都写在组件外,引用天然稳定,不需要额外包 useCallback。
getSnapshot 必须返回稳定的值
这是最容易踩的坑。每次调用都新建对象,React 会认为值一直在变:
// 错:每次调用都返回新对象function getSnapshot() { return { width: window.innerWidth, height: window.innerHeight }}控制台会报「The result of getSnapshot should be cached」。窗口宽度这种单个值直接返回原始类型即可:
function getSnapshot() { return window.innerWidth}确实需要返回对象时,自己缓存:数据没变就把上一次那个对象原样返回。
subscribe 也要稳定
把 subscribe 定义在组件内,每次渲染都是新函数,React 会退订再重新订阅。定义在组件外即可;订阅需要跟着参数走(比如房间号)时,用 useCallback 把它固定住。
服务端渲染
省略第三个参数时,组件在服务端渲染会抛错。给它一个服务端也成立的快照,比如 () => true,并保证服务端与水合时用的值一致。
并发下它保证什么
外面的世界不受 React 调度控制。一次非阻塞更新期间外部数据被改动时,React 会把这次更新降级为阻塞更新、从头重算,避免屏幕上同时出现两个版本的数据。这就是它存在的理由。
几条边界
别用外部快照的值来挂起渲染,比如据此条件渲染一个 lazy 组件。官方不建议这么做:外部数据的变化无法标记为非阻塞更新,会把屏幕上已经显示的内容换成加载态。
读到的值不要自己改,它属于外部数据源。用现成的状态库时,直接用库提供的 Hook,不必自己写这一层。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时