认识
一、认识
以往基于 useState
、useReducer
和 useEffect
、useLayoutEffect
订阅外部状态源, 触发更新的方式是基于 useState
或者 useReducer
的 dispatch
。在 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
并发模式下的规则,我们会把更新过程中需要执行的任务划分优先级,优先级低的有可能会被打断。假设某个任务A
和B
,都同时依赖了外部状态中的某个State
,在Rerender
开始时,值为1
,任务A
执行完之后,React
把线程的处理权交还给了浏览器,浏览器的某些操作导致了这个State
的值变成了2
。那么等到B
任务重新恢复执行时,读到的值就会出现差异,导致渲染结果的不一致性。因此 useSyncExternalStore
应运而生。
useSyncExternalStore
是 React 18
中推出的一个新的 Hook
, 专门用于在 React
组件内部同步读取并订阅外部状态源, 是一种更加可靠、高效的方式来订阅 React
组件外的数据源的方案。 useSyncExternalStore
通过 subscribe
来订阅外部状态源, 后续状态源更新时, 调用 subscribe
回调, 告知 React
进行内部自动更新。useSyncExternalStore
通过 getSnapshot
同步读取外部状态源。
useSyncExternalStore
通过在 subscribe
和 getSnapshot
中实现精细的控制,保证了组件中的状态与外部数据源保持同步。这种机制确保了即使在应用中存在高频率的状态更新或并发模式下也不会出现状态不一致的情况。在 Concurrent Mode
下, React
可能会中断、暂停或重启渲染过程, useSyncExternalStore
可以确保即使在这种情况下,通过 getSnapshot
获取的状态仍然是最新的,有效避免了因组件状态与外部数据源不同步产生的问题。
二、工作
useSyncExternalStore
在组件首次渲染时, useSyncExternalStore
会执行 subscribe
函数进行订阅。订阅的回调函数中,React
会将组件标记为需要更新,当外部数据发生变化时将其重新渲染。与此同时,getSnapshot
函数会被用来获取当前的状态值,以便在组件中使用。在组件卸载时,useSyncExternalStore
会自动执行取消订阅的操作,防止内存泄露。
其中, useSyncExternalStore
保证 store
状态一致的手段就是协调采用 Sync
不可中断渲染。为了达到这个目的,useSyncExternalStore
采用了三道保险:
-
通过
dispatch
修改store
状态时,强制使用Sync
同步不可中断渲染 -
Concurrent
模式下,协调结束以后会进行一致性检查,如果发现状态不一致,强制重新进行一次Sync
同步不可中断渲染 -
commit
阶段时,再进行一次一致性检查,如果发现状态不一致,强制重新进行一次Sync
同步不可中断渲染