认识
一、认识
以往基于 useState
、useReducer
和 useEffect
、useLayoutEffect
订阅外部状态源。在 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
获取的状态仍然是最新的,有效避免了因组件状态与外部数据源不同步产生的问题。
二、语法
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
的当前快照,可以在你的渲染逻辑中使用。