源码
useSyncExternalStoreWithSelector
ts
// Same as useSyncExternalStore, but supports selector and isEqual arguments.
// 支持selector和isEqual
export function useSyncExternalStoreWithSelector<Snapshot, Selection>(
subscribe: (() => void) => () => void,
getSnapshot: () => Snapshot,
getServerSnapshot: void | null | (() => Snapshot),
selector: (snapshot: Snapshot) => Selection,
isEqual?: (a: Selection, b: Selection) => boolean,
): Selection {
// Use this to track the rendered snapshot.
const instRef = useRef(null);
let inst;
if (instRef.current === null) {
inst = {
hasValue: false,
value: (null: Selection | null),
};
instRef.current = inst;
} else {
inst = instRef.current;
}
const [getSelection, getServerSelection] = useMemo(() => {
// Track the memoized state using closure variables that are local to this
// memoized instance of a getSnapshot function. Intentionally not using a
// useRef hook, because that state would be shared across all concurrent
// copies of the hook/component.
// 闭包记忆当前的snapshot和selection
let hasMemo = false;
let memoizedSnapshot;
let memoizedSelection;
const memoizedSelector = nextSnapshot => {
if (!hasMemo) {
// The first time the hook is called, there is no memoized result.
hasMemo = true;
memoizedSnapshot = nextSnapshot;
// 根据selector获取selection(Snapshot)
const nextSelection = selector(nextSnapshot);
if (isEqual !== undefined) {
// Even if the selector has changed, the currently rendered selection
// may be equal to the new selection. We should attempt to reuse the
// current value if possible, to preserve downstream memoizations.
if (inst.hasValue) {
const currentSelection = inst.value;
if (isEqual(currentSelection, nextSelection)) {
memoizedSelection = currentSelection;
return currentSelection;
}
}
}
memoizedSelection = nextSelection;
return nextSelection;
}
// We may be able to reuse the previous invocation's result.
const prevSnapshot: Snapshot = (memoizedSnapshot: any);
const prevSelection: Selection = (memoizedSelection: any);
if (is(prevSnapshot, nextSnapshot)) {
// The snapshot is the same as last time. Reuse the previous selection.
return prevSelection;
}
// The snapshot has changed, so we need to compute a new selection.
const nextSelection = selector(nextSnapshot);
// If a custom isEqual function is provided, use that to check if the data
// has changed. If it hasn't, return the previous selection. That signals
// to React that the selections are conceptually equal, and we can bail
// out of rendering.
if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
return prevSelection;
}
memoizedSnapshot = nextSnapshot;
memoizedSelection = nextSelection;
return nextSelection;
};
// Assigning this to a constant so that Flow knows it can't change.
const maybeGetServerSnapshot =
getServerSnapshot === undefined ? null : getServerSnapshot;
// 客户端getSnapshotWithSelector <= memoizedSelector
const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
const getServerSnapshotWithSelector =
maybeGetServerSnapshot === null
? undefined
: () => memoizedSelector(maybeGetServerSnapshot());
return [getSnapshotWithSelector, getServerSnapshotWithSelector];
// selector函数不要每次都动态创建
}, [getSnapshot, getServerSnapshot, selector, isEqual]);
// 调用react useSyncExternalStore hook
const value = useSyncExternalStore(
subscribe,
getSelection,
getServerSelection,
);
// isEqual入参逻辑需要,实质是给instRef.current地址更新值
useEffect(() => {
inst.hasValue = true;
inst.value = value;
}, [value]);
useDebugValue(value);
return value;
}
源码解析
getSelection
根据selector入参获取Snapshot相应属性
ts
const [getSelection, getServerSelection] = useMemo(() => {
// Track the memoized state using closure variables that are local to this
// memoized instance of a getSnapshot function. Intentionally not using a
// useRef hook, because that state would be shared across all concurrent
// copies of the hook/component.
let hasMemo = false;
let memoizedSnapshot;
let memoizedSelection;
const memoizedSelector = nextSnapshot => {
if (!hasMemo) {
// The first time the hook is called, there is no memoized result.
hasMemo = true;
memoizedSnapshot = nextSnapshot;
const nextSelection = selector(nextSnapshot);
if (isEqual !== undefined) {
// Even if the selector has changed, the currently rendered selection
// may be equal to the new selection. We should attempt to reuse the
// current value if possible, to preserve downstream memoizations.
if (inst.hasValue) {
const currentSelection = inst.value;
if (isEqual(currentSelection, nextSelection)) {
memoizedSelection = currentSelection;
return currentSelection;
}
}
}
memoizedSelection = nextSelection;
return nextSelection;
}
// We may be able to reuse the previous invocation's result.
const prevSnapshot: Snapshot = (memoizedSnapshot: any);
const prevSelection: Selection = (memoizedSelection: any);
if (is(prevSnapshot, nextSnapshot)) {
// The snapshot is the same as last time. Reuse the previous selection.
return prevSelection;
}
// The snapshot has changed, so we need to compute a new selection.
const nextSelection = selector(nextSnapshot);
// If a custom isEqual function is provided, use that to check if the data
// has changed. If it hasn't, return the previous selection. That signals
// to React that the selections are conceptually equal, and we can bail
// out of rendering.
if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
return prevSelection;
}
memoizedSnapshot = nextSnapshot;
memoizedSelection = nextSelection;
return nextSelection;
};
// Assigning this to a constant so that Flow knows it can't change.
const maybeGetServerSnapshot =
getServerSnapshot === undefined ? null : getServerSnapshot;
const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
const getServerSnapshotWithSelector =
maybeGetServerSnapshot === null
? undefined
: () => memoizedSelector(maybeGetServerSnapshot());
return [getSnapshotWithSelector, getServerSnapshotWithSelector];
}, [getSnapshot, getServerSnapshot, selector, isEqual]);
getSelection
函数根据selector
获取Snapshot
的某个属性。 这里需要注意useMemo
;在使用这hooks尽量保证selector
、isEqual
是immutable
isEqual
判断原数据和当前数据是否一致(浅比较)
ts
if (!hasMemo) {
// The first time the hook is called, there is no memoized result.
hasMemo = true;
memoizedSnapshot = nextSnapshot;
const nextSelection = selector(nextSnapshot);
if (isEqual !== undefined) {
// Even if the selector has changed, the currently rendered selection
// may be equal to the new selection. We should attempt to reuse the
// current value if possible, to preserve downstream memoizations.
// 判断新selection和旧的selection是否一致
if (inst.hasValue) {
const currentSelection = inst.value;
if (isEqual(currentSelection, nextSelection)) {
memoizedSelection = currentSelection;
return currentSelection;
}
}
}
memoizedSelection = nextSelection;
return nextSelection;
}
这里用到的inst最新值是通过useEffect实时更新的;如果不用
immer
相关库或者不做好immutable数据处理会导致不必要更新
ts
useEffect(() => {
inst.hasValue = true;
inst.value = value;
}, [value]);