第七章: useSyncExternalStore源码解析

前言

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

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

源码解析

// 和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对象,

 const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  }

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

const nextSnapshot = getSnapshot();
hook.memoizedState = nextSnapshot;
const inst = {
    value: nextSnapshot,
    getSnapshot: getSnapshot
}
 hook.queue = inst

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

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方法,

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返回值);

updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe])

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

同样更新pushEffect部分的hook

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。

原文链接:https://juejin.cn/post/7316202589603315739 作者:不会c的半吊子前端开发的

(0)
上一篇 2023年12月25日 下午4:31
下一篇 2023年12月25日 下午4:41

相关推荐

发表回复

登录后才能评论