useSyncExternalStore
useSyncExternalStore
允许应用订阅一个外部的 Store,但是这个 Store 需要是同步的。useSyncExternalStore
的使用格式如下:
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
useSyncExternalStore
在使用的时候,最常用的用法是提供其中前两个函数,其中subscribe
用来订阅 Store,并返回一个取消订阅的函数,getSnapshot
用来从 Store 中读取数据。
以下实现一个可以被useSyncExternalStore
订阅的 Store。
const itemsStore = {
addItem(info) {
items = [...items, { id: max(pluck("id", items)) + 1, text: info }];
emitChange();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listener.filter((l) => l !== listener);
};
},
getSnapshot() {
return items;
},
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
然后就可以使用useSyncExternalStore
来订阅这个 Store 了。
function ItemList() {
const items = useSyncExternalStore(
itemsStore.subscribe,
itemsStore.getSnapshot
);
return (
<>
<div>
<button onClick={() => itemsStore.addItem(`${Math.random()}`)}>
Add
</button>
</div>
<ul>
{items.map((item) => (
<li key={item.id}>{items.text}</li>
))}
</ul>
</>
);
}
在使用useSyncExternalStore
的时候需要注意以下几点:
getSnapshot
返回的 Store 快照内容必须是不可变的,而且在 Store 没有发生变化的时候,getSnapshot
返回的内容应该始终是一致的。- React 会使用
Object.is
来对比getSnapshot
返回的内容,以决定是否需要重新渲染组件。 - 如果在重新渲染的时候传入了一个不同的
subscribe
函数,那么 React 会使用新传入的subscribe
函数重新订阅 Store。所以为了避免subscribe
函数发生改变,应该像上面示例中一样在组件外部定义订阅函数。 - 如果在非阻塞 transition 更新过程中更新了 Store,那么 React 将回退并视此次更新为阻塞更新。换句话说,在每次 transition 更新的时候,React 将在更改应用到 DOM 之前第二次调用
getSnapshot
,如果此次的返回值与之前的返回值不同,那么 React 将重新开始更新过程。 - 不要使用
useSyncExternalStore
返回的 Store 内容决定渲染状态。
例如根据上面最后一条的内容,下面的用法是不建议使用的。
function IllegalStoreSwitch() {
const selectedItemId = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return selectedItemId != null ? <ItemDetailPage /> : <FeaturedItemPage />;
}