第七章: useSyncExternalStore源码解析

前言

这个是关于hook可能导致组件更新的最后一个,前面已经讲了关于能够更新的hook有useState ,useReducer ,useTransition ,useDeferredValue ,实际上useDeferredValue 是没有自己能够主动重新渲染的能力的,依赖于其他方法触发渲染,而其他三种是能够主动触发的;

阅读本章之前建议先读mountEffect,有一些源码是依赖于useEffect的。

源码解析

js 复制代码
// 和useEffect执行时机一致,返回一个函数
const subscribe = (handleStoreChange)=>{
}
// 在createRoot模式下调用
const getSnapshot = () => {
}

// 针对与hydrateRoot模式调用
function getServerSnapshot() {
}
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)

mount阶段

按照常规流程,调用HooksDispatcherOnMountInDEV.useSyncExternalstore 的方法,前置check检查方法,核心代码mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) ;

首先调用mountWorkInProgressHook方法,建立一个hook对象,

js 复制代码
 const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  }

判断当前渲染的模式,是调用getSnapshot 方法还是getServerSnapshot 方法,调用两次*Snapshot方法,将两次返回值进行Object.is比较,如果不同会有个error提示;

js 复制代码
const nextSnapshot = getSnapshot();
hook.memoizedState = nextSnapshot;
const inst = {
    value: nextSnapshot,
    getSnapshot: getSnapshot
}
 hook.queue = inst

调用mountEffect方法,这个是useEffect核心方法,

js 复制代码
function checkIfSnapshotChanged(inst) {
  var latestGetSnapshot = inst.getSnapshot;
  var prevValue = inst.value;
  try {
    var nextValue = latestGetSnapshot();
    return !objectIs(prevValue, nextValue);
  } catch (error) {
    return true;
  }
}
function subscribeToStore(fiber, inst, subscribe) {
  var handleStoreChange = function () {
    if (checkIfSnapshotChanged(inst)) {
      // Force a re-render.
      forceStoreRerender(fiber);
    }
  }; 
  return subscribe(handleStoreChange);
}
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
const Passive = 0b00000000000000100000000000;
fiber.flags |= Passive;

关于mountEffect的代码会放在mountEffect章节中讲解; 主要是创建一个hook对象,和调用pushEffect 方法,

本身也会调用一次pushEffect方法,

js 复制代码
const HasEffect = 0b0001;
const Passive = 0b1000;
function updateStoreInstance<T>(
  fiber,
  inst,
  nextSnapshot,
  getSnapshot,
) {
  inst.value = nextSnapshot;
  inst.getSnapshot = getSnapshot;
  if (checkIfSnapshotChanged(inst)) {
    // Force a re-render.
    forceStoreRerender(fiber);
  }
}
pushEffect(
    HasEffect | Passive, // flags状态
    updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
    undefined,
    null,
);

nextSnapshot返回。

fiber.updateQueue的任务队列中会创建两个链表,一个是mountEffect创建的,一个是pushEffect创建的;

在执行useEffect时机时,参考commitRoot阶段,遍历updateQueue队列,首先执行mountEffect任务,会执行subscribe 方法,传入一个函数handleStoreChange , 然后执行pushEffect任务,执行updateStoreInstance函数,会再次调用getSnapshot方法,对比之前的值,决定是否调用forceStoreRerender方法;

调用handleStoreChange方法

调用handleStoreChange 更新方法,就是调用subscribe中的行参的方法,内部调用getSnapshot方法,将上次和这次的返回值对比,如果不相同就进行重新渲染;

update 阶段

调用HooksDispatcherOnUpdateInDEV.useSyncExternalStore 方法,调用check方法;

调用updateWorkInProgressHook方法,浅copy hook对象,调用两次getSnapshot方法,对比返回值,如果不同会报错,将getSnapshot返回结果更新到hook.memoizedState(上一次调用getSnapshot返回值);

js 复制代码
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe])

updateEffect是useEffect更新阶段核心代码,在mountEffect更新部分中讲解;

同样更新pushEffect部分的hook

js 复制代码
fiber.flags |= Passive;
pushEffect(
  HookHasEffect | HookPassive,
  updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
  undefined,
  null,
);

返回最新的nextSnapshot

同样在更新阶段执行useEffect时机时,参考useEffect中update阶段判断,也是在当前任务队列中执行;

根据updateEffect的核心代码,判断依赖项subscribe是否有改变,没有的话就不执行getSnapshot和返回函数,继续执行pushEffect创建的updateStoreInstance的函数,对比getSnapshot和返回值和上次的值,判断是否需要更新;

总结

目前还没找到很好的单独应用实例,据说,我听说最新版的redux就是使用useSyncExternalStore。

相关推荐
01传说15 分钟前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js
Misha韩21 分钟前
React Native 一些API详解
react native·react.js
小李飞飞砖22 分钟前
React Native 组件间通信方式详解
javascript·react native·react.js
小李飞飞砖22 分钟前
React Native 状态管理方案全面对比
javascript·react native·react.js
烛阴1 小时前
Python装饰器解除:如何让被装饰的函数重获自由?
前端·python
千鼎数字孪生-可视化2 小时前
Web技术栈重塑HMI开发:HTML5+WebGL的轻量化实践路径
前端·html5·webgl
凌辰揽月2 小时前
7月10号总结 (1)
前端·css·css3
天天扭码2 小时前
很全面的前端面试——CSS篇(上)
前端·css·面试
EndingCoder2 小时前
搜索算法在前端的实践
前端·算法·性能优化·状态模式·搜索算法
sunbyte2 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DoubleVerticalSlider(双垂直滑块)
前端·javascript·css·vue.js·vue