React 用 useSyncExternalStore 接入外部可变数据源:浏览器 API、第三方 store 与订阅

test12026-10-030 次阅读

为什么需要 useSyncExternalStore

React 18 引入的 useSyncExternalStore 是读取“存在于 React 之外的可变数据”的官方方案。过去我们常用 useState 加 useEffect 去订阅浏览器 API(如 online 状态、localStorage、媒体查询)或第三方状态库,但这类写法在并发渲染下容易出现撕裂(tearing)——同一个渲染结果里读到了新旧两个状态。

useSyncExternalStore 把“订阅”和“读取快照”拆成两个纯函数,React 能在渲染前强制拿到一致的快照,从根本上规避撕裂。

最小可用签名

它接收三个参数:subscribe(订阅函数)、getSnapshot(返回当前快照)、以及可选的 getServerSnapshot(SSR 时使用)。

import { useSyncExternalStore } from "react";

function useOnlineStatus() {
  const isOnline = useSyncExternalStore(
    (callback) => {
      window.addEventListener("online", callback);
      window.addEventListener("offline", callback);
      return () => {
        window.removeEventListener("online", callback);
        window.removeEventListener("offline", callback);
      };
    },
    () => navigator.onLine,
    () => true
  );
  return isOnline;
}

封装一个可复用的 localStorage store

把任意 localStorage 键封装成响应式数据源,订阅 window 的 storage 事件即可在多标签页间同步。

function createStorageStore(key: string) {
  const listeners = new Set<() => void>();
  window.addEventListener("storage", (e) => {
    if (e.key === key) listeners.forEach((l) => l());
  });
  return {
    subscribe(listener: () => void) {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
    getSnapshot() {
      return window.localStorage.getItem(key);
    },
  };
}

const themeStore = createStorageStore("theme");

function useTheme() {
  return useSyncExternalStore(
    themeStore.subscribe,
    themeStore.getSnapshot,
    () => "light"
  );
}

接入第三方 store 的注意事项

  • getSnapshot 必须返回缓存值:如果每次都返回新对象(如展开运算符造对象),React 会陷入无限重渲染,应在 store 内部保留上一次引用的缓存。
  • 订阅函数必须返回取消订阅函数,否则组件卸载时会泄漏监听器。
  • 快照要可比较:基本类型、字符串、或稳定引用的对象最安全;返回数组或对象时记得做引用缓存。

掌握这条规则后,任何“React 之外的真相来源”都能干净地接进组件树,而不必再为了规避撕裂去堆 useEffect 和 useState。

T

test1

文章作者

为什么需要 useSyncExternalStore React 18 引入的 useSyncExternalStor...

分类
技术
发布时间
2026-10-03
字数
约 1791 字
阅读
0 次