跳到主要内容

认识

2024年02月24日
柏拉文
越努力,越幸运

一、认识


以往基于 useStateuseReduceruseEffectuseLayoutEffect 订阅外部状态源。在 React 18 之后的并发模式下, 会发生组件状态与外部数据源不同步而导致渲染不一致, 进而渲染撕裂的问题。如下所示:

export const useOutSideStore = (store) => {
const [state, setState] = useState(store.getState());

useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});

return () => { unsubscribe(); };
}, []);

return state;
}

我们来思考这样一种场景: 假设我们的现在的页面触发了更新,需要进行Rerender,而根据Concurrent Mode并发模式下的规则,我们会把更新过程中需要执行的任务划分优先级,优先级低的有可能会被打断。假设某个任务AB,都同时依赖了外部状态中的某个State,在Rerender开始时,值为1,任务A执行完之后,React把线程的处理权交还给了浏览器,浏览器的某些操作导致了这个State的值变成了2。那么等到B任务重新恢复执行时,读到的值就会出现差异,导致渲染结果的不一致性。因此 useSyncExternalStore 应运而生。

useSyncExternalStoreReact 18 中推出的一个新的 Hook, 专门用于在 React 组件内部同步读取并订阅外部状态源, 是一种更加可靠、高效的方式来订阅 React 组件外的数据源的方案。 useSyncExternalStore 通过 subscribe 来订阅外部状态源, 后续状态源更新时, 调用 subscribe 回调, 告知 React 进行内部自动更新。useSyncExternalStore 通过 getSnapshot 同步读取外部状态源。

useSyncExternalStore 通过在 subscribegetSnapshot 中实现精细的控制,保证了组件中的状态与外部数据源保持同步。这种机制确保了即使在应用中存在高频率的状态更新或并发模式下也不会出现状态不一致的情况。在 Concurrent Mode 下, React 可能会中断、暂停或重启渲染过程, useSyncExternalStore 可以确保即使在这种情况下,通过 getSnapshot 获取的状态仍然是最新的,有效避免了因组件状态与外部数据源不同步产生的问题。

二、语法


const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
  • subscribe: 一个函数,接收一个单独的 callback 参数并把它订阅到 store 上。当 store 发生改变,它应当调用被提供的 callback。这会导致组件重新渲染。subscribe 函数会返回清除订阅的函数。

  • getSnapshot: 一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用 getSnapshot 必须返回同一个值。如果 store 改变,并且返回值也不同了(用 Object.is 比较),React 就会重新渲染组件。

  • getServerSnapshot: 一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。

  • snapshot: 该 store 的当前快照,可以在你的渲染逻辑中使用。