useSyncExternalStoreWithSelector源码

源码

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尽量保证selectorisEqual是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]);
相关推荐
前端小巷子12 分钟前
跨域问题解决方案:JSONP
前端·javascript·面试
eric*168820 分钟前
尚硅谷张天禹老师课程配套笔记
前端·vue.js·笔记·vue·尚硅谷·张天禹·尚硅谷张天禹
程序员爱钓鱼36 分钟前
Go语言中的反射机制 — 元编程技巧与注意事项
前端·后端·go
GIS之路1 小时前
GeoTools 结合 OpenLayers 实现属性查询(二)
前端·信息可视化
烛阴1 小时前
一文搞懂 Python 闭包:让你的代码瞬间“高级”起来!
前端·python
AA-代码批发V哥1 小时前
HTML之表单结构全解析
前端·html
聪聪的学习笔记1 小时前
【1】确认安装 Node.js 和 npm版本号
前端·npm·node.js
小磊哥er2 小时前
【前端工程化】你知道前端编码规范包含哪些内容吗
前端
菌菇汤2 小时前
uni-app实现单选,多选也能搜索,勾选,选择,回显
前端·javascript·vue.js·微信小程序·uni-app·app
Ramos丶2 小时前
【ABAP】 从无到有 新建一个Webdynpro程序
java·前端·javascript