useSyncExternalStore:同步 React 与 localStorage 的正确方式

发布: (2026年1月9日 GMT+8 12:00)
3 min read
原文: Dev.to

Source: Dev.to

Introduction

React 18 引入了一个低层次的 Hook,大多数开发者从未直接使用过,但几乎所有现代状态库都依赖它:useSyncExternalStore
它是 React 官方连接组件与外部状态源的方式——这些状态存在于 React 之外,例如:

  • localStorage
  • Redux / Zustand store
  • 浏览器 API
  • WebSocket 数据

Why useSyncExternalStore?

在 React 18 之前,订阅外部 store 通常这样写:

useEffect(() => store.subscribe(forceUpdate), []);

这种模式在并发渲染下会出问题。useSyncExternalStore 通过让 React 控制以下内容来解决该问题:

  • 订阅 更改
  • 读取 当前值
  • 安全触发 重新渲染
const value = useSyncExternalStore(
  subscribe,
  getSnapshot,
  getServerSnapshot? // optional, for server‑side rendering
);
  • subscribe – React 如何监听更新
  • getSnapshot – React 如何读取当前状态

当快照变化时,React 会自动重新渲染。

Creating a localStorage store

function createLocalStorageStore(key, initialValue) {
  const listeners = new Set();

  const getSnapshot = () => {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : initialValue;
  };

  const setValue = (value) => {
    localStorage.setItem(key, JSON.stringify(value));
    listeners.forEach((l) => l());
  };

  const subscribe = (listener) => {
    listeners.add(listener);

    const onStorage = (e) => {
      if (e.key === key) listener();
    };

    window.addEventListener("storage", onStorage);

    return () => {
      listeners.delete(listener);
      window.removeEventListener("storage", onStorage);
    };
  };

  return { getSnapshot, setValue, subscribe };
}

Hook wrapper for external stores

import { useSyncExternalStore } from "react";

function useExternalStore(store) {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot,
    store.getSnapshot // same function for server snapshot if needed
  );
}

Example: Counter synced with localStorage

const counterStore = createLocalStorageStore("counter", 0);

function Counter() {
  const count = useExternalStore(counterStore);

  return (
    <>
      Count: {count}
      {/* The original snippet omitted the button element; kept as‑is */}
      counterStore.setValue(count + 1)}>+
    </>
  );
}

现在计数器状态是:

  • 持久化(保存在 localStorage 中)
  • 共享 于多个组件之间
  • 跨标签页同步,通过 storage 事件实现
  • 并发安全,得益于 useSyncExternalStore

When to use useSyncExternalStore

  • 状态存在于 React 之外(例如浏览器存储、外部 store、WebSocket 数据)。
  • 多个组件需要 共享 同一数据。
  • 正在构建 store持久化层,供其他组件使用。

对于普通的组件本地状态,仍然使用 useStateuseState 管理 React 自身的状态,而 useSyncExternalStore 用于将 React 与外部世界连接。如果你要构建的东西超出本地组件状态,这个 Hook 就是基础。

Back to Blog

相关文章

阅读更多 »