← 返回题目列表

useSyncExternalStore 解决了什么问题?

困难 第 27 / 27 题 更新于 2026/07/29
ReactuseSyncExternalStore外部状态并发渲染

简化版

useSyncExternalStore 用来安全订阅 React 外部的数据源,例如 Redux store、浏览器 API、手写状态容器。它让 React 在并发渲染下能一致地读取快照,避免 tearing,也提供 SSR 快照能力。

详细版

外部 store 不由 React state 管理,但 React 组件需要订阅它。

const value = useSyncExternalStore(
  store.subscribe,
  store.getSnapshot,
  store.getServerSnapshot
)

subscribe 负责订阅变化,getSnapshot 返回当前快照,getServerSnapshot 用于 SSR。它主要给状态库作者和复杂外部订阅场景使用。普通组件内部状态仍然用 useStateuseReducer,不要为了显得高级滥用它。

完整版教学

一、外部状态不在 React 的管理范围内

React 自己的 state 更新能被调度和追踪。 但 Redux store、浏览器在线状态、localStorage 事件、WebSocket 缓存等数据源,可能在 React 外部变化。 组件想读取这些数据,就需要建立订阅。

function useOnlineStatus() {
  return useSyncExternalStore(
    subscribeOnline,
    () => navigator.onLine,
    () => true
  )
}

如果直接在 render 中读取外部变量,React 不知道它什么时候变。 如果只用 effect 手动 setState,在并发渲染下又可能出现一致性问题。

二、它的三个参数分工很明确

subscribe 告诉 React 数据源变了怎么通知。 getSnapshot 告诉 React 当前值是什么。 getServerSnapshot 告诉 React 服务端渲染时快照是什么。

function subscribe(callback: () => void) {
  store.on('change', callback)
  return () => store.off('change', callback)
}

function getSnapshot() {
  return store.getState()
}
参数职责注意点
subscribe订阅外部变化必须返回取消订阅函数
getSnapshot读取客户端快照相同数据应返回相同引用
getServerSnapshot读取服务端快照SSR 时保持一致

如果 getSnapshot 每次都返回新对象,即使数据没变,也可能导致无限重渲染。 快照的稳定性是这题的重要细节。

三、它防的是 tearing 这类一致性问题

tearing 可以理解成同一次渲染里,不同组件看到外部 store 的不同版本。 并发渲染允许 React 暂停和恢复,如果外部数据在中间变化,就可能出现不一致。 useSyncExternalStore 给 React 一个标准协议,让它能在提交前检查快照一致性。

组件 A 读到 store v1
渲染暂停
store 变成 v2
组件 B 读到 store v2
同一屏出现 v1 和 v2 -> tearing

这个问题在普通小页面不常被肉眼看到。 但对状态库、协作编辑、实时数据面板来说非常关键。

四、外部 store 的快照要可缓存

getSnapshot 返回值用于比较是否需要重新渲染。 如果每次都返回 { ...state } 新对象,React 会认为快照一直变。 正确做法是 store 内部维护不可变状态,没变化时返回同一个引用。

// 不推荐
function getSnapshot() {
  return { ...store.state }
}

// 推荐
function getSnapshot() {
  return store.state
}

假设组件每秒渲染 10 次,getSnapshot 每次都返回新对象,就可能制造 10 次无意义更新。 大型应用里这种细节会变成性能坑。

五、SSR 需要 getServerSnapshot 保持初始一致

服务端渲染时没有浏览器环境,也可能没有客户端 store 的最新状态。 getServerSnapshot 用来提供服务端快照。 客户端 hydration 时,它应与初始客户端快照匹配,否则会出现不一致。

const theme = useSyncExternalStore(
  subscribeTheme,
  getClientTheme,
  () => 'light'
)

如果服务端返回 light,客户端首次读取 dark,页面可能 hydration 不匹配。 这时要通过内联脚本、cookie 或服务端请求头让初始主题一致。

六、普通业务组件不一定需要直接用它

多数开发者通过 Redux、Zustand 等库间接使用它。 这些库会封装外部 store 订阅细节。 只有你自己写状态库、订阅浏览器外部源、做实时数据容器时,才需要直接掌握。

记忆钩子:useSyncExternalStore 是 React 和外部 store 之间的同步协议。

面试回答不要把它说成普通状态 Hook。 它的关键词是外部数据源、订阅、快照、一致性和 SSR。

七、常见误区与追问

  • 误区:useSyncExternalStore 可以替代 useState。 它服务外部 store 订阅,组件内部状态仍用 useStateuseReducer
  • 误区:getSnapshot 每次返回新对象没问题。 快照引用不稳定会导致不必要渲染,甚至循环更新。
  • 误区:subscribe 不需要清理。 必须返回取消订阅函数,否则会造成泄漏和重复通知。
  • 追问:tearing 是什么? 同一轮 UI 中不同组件看到外部 store 的不同版本,造成界面不一致。
  • 追问:状态库为什么会用它? 状态库的数据在 React 外部,需要用标准订阅协议适配并发渲染。
  • 追问:SSR 为什么需要第三个参数? 服务端没有客户端环境,需要提供可匹配的初始快照。

八、加强记忆

这题按“外部 store 三件套”记:订阅变化、读取快照、服务端快照。它不是普通 state API,而是保证 React 与外部状态在并发和 SSR 下安全对接的协议。重点补上 tearing 和快照引用稳定性。