Appearance
useSyncExternalStore
这个不在 React 内部,而是单独一个 package 维护的一个 API, 主要用于第三方包,提供内置的发布订阅者模式能力,为 React 状态管理库提供了解决方案。
说明
js
const state = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().data
);store.subscribe: 订阅函数,当 state 的值发生变化的时候可以通过这个函数通知订阅者更新() => store.getSnapshot().data: 获取 state 的方法
例子
jsx
const listeners = [];
// 构建一个组件外部 store
const store = {
inc(num = 1) {
states = {
...states,
count: states.count + num,
};
// 通知订阅者更新
this.emitChange();
},
emitChange() {
listeners.map((listener) => listener?.());
},
// 订阅
subscribe(listener: () => void) {
listeners = [...listeners, listener];
// 返回取消订阅的函数
return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
getSnapshot() {
// 返回属性的快照
return states;
},
};源码阅读
js
export function useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T
): T {
// 初始化获取一个当前 store 的快照信息
const value = getSnapshot();
// 使用 useState 来提供react中组件更新的能力
const [{ inst }, forceUpdate] = useState({ inst: { value, getSnapshot } });
useLayoutEffect(() => {
// 赋值
inst.value = value;
// 缓存 获取快照的函数
inst.getSnapshot = getSnapshot;
// 判断是否更新
if (checkIfSnapshotChanged(inst)) {
// 如果更新了,那就触发组件的更新
forceUpdate({ inst });
}
}, [subscribe, value, getSnapshot]);
useEffect(() => {
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({ inst });
}
// 提供订阅当前组件的能力,
const handleStoreChange = () => {
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({ inst });
}
};
return subscribe(handleStoreChange);
}, [subscribe]);
useDebugValue(value);
return value;
}对于状态管理库,其最重要的是两个方面:
- 数据的管理 和 判断更新
- 数据更新后通知订阅者更新
这个 API 主要不是解决第一个问题,即怎么去维护和管理数据,而是主要负责在数据更新后触发当前组件的更新能力,
在 React 中对于函数式组件触发更新的方式主要就是 setState , 所以这个方法的核心也是创建了一个 state,即const [{ inst }, forceUpdate] = useState({ inst: { value, getSnapshot } });
他不关心数据更新后怎么去更新数据,而是提供了一个 forceUpdate 方法去触发组件的更新能力,这样当第二个参数 () => store.getSnapshot().data 返回的新旧 state 的值发生更新的时候,就可以通过 forceUpdate 去触发当前组件的重新渲染了,这时候就引发了下面几个问题:
- 新旧 state 值怎么判断是否更新
具体看下面这个方法
js
function checkIfSnapshotChanged(inst) {
const latestGetSnapshot = inst.getSnapshot;
const prevValue = inst.value;
try {
const nextValue = latestGetSnapshot();
return !is(prevValue, nextValue);
} catch (error) {
return true;
}
}当通过 inst.getSnapshot() 获取到一个新的 nextValue 的时候 ,通过 is() 方法去判断 新旧 state 是否相同,那么就可以看出新旧值是一个浅比较的逻辑。
- 订阅者能力
当我们的 state 的数据发生了更新,那么是不是需要通知所有的订阅者去更新,那我们怎么知道有哪些订阅者呐,
所以useSyncExternalStore(store.subscribe) 的第一个参数就是提供一个订阅 当前 state 更新的订阅者的回调函数 即 store.subscribe = (listener) => this.listeners.push(l) , 这样在 store 中就可以通过 this.listeners.map(l => l()) 的回调方式触发当前组件是否需要更新的能力,即
js
useEffect(() => {
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({ inst });
}
const handleStoreChange = () => {
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceUpdate({ inst });
}
};
return subscribe(handleStoreChange);
}, [subscribe]);