跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


以往基于 useStateuseReduceruseEffectuseLayoutEffect 订阅外部状态源, 触发更新的方式是基于 useState 或者 useReducerdispatch。在 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 获取的状态仍然是最新的,有效避免了因组件状态与外部数据源不同步产生的问题。

二、工作


useSyncExternalStore 在组件首次渲染时, useSyncExternalStore 会执行 subscribe 函数进行订阅。订阅的回调函数中,React 会将组件标记为需要更新,当外部数据发生变化时将其重新渲染。与此同时,getSnapshot 函数会被用来获取当前的状态值,以便在组件中使用。在组件卸载时,useSyncExternalStore 会自动执行取消订阅的操作,防止内存泄露。

其中, useSyncExternalStore 保证 store 状态一致的手段就是协调采用 Sync 不可中断渲染。为了达到这个目的,useSyncExternalStore 采用了三道保险:

  1. 通过 dispatch 修改 store 状态时,强制使用 Sync 同步不可中断渲染

  2. Concurrent 模式下,协调结束以后会进行一致性检查,如果发现状态不一致,强制重新进行一次 Sync 同步不可中断渲染

  3. commit 阶段时,再进行一次一致性检查,如果发现状态不一致,强制重新进行一次 Sync 同步不可中断渲染