第七章: 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。

相关推荐
一斤代码5 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子5 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子6 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina6 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路7 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码7 小时前
1.
react.js·node.js·angular.js
伍哥的传说7 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409197 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app