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

相关推荐
咩咩啃树皮2 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
阳光是sunny2 小时前
LangGraph中的Reducer是什么
前端·人工智能·后端
触底反弹2 小时前
一文搞懂 Tailwind CSS 弹性布局:从原理到实战
前端·css·html
阳光是sunny2 小时前
从链到图:LangGraph 入门基础全解析
前端·人工智能·后端
小林ixn3 小时前
从 ??= 到 onKeyDown:一个 React 组件的“自我修养”
前端·javascript·react.js
REDcker3 小时前
显示分辨率标准对照详解
前端·网络·分辨率·显示·屏幕
এ慕ོ冬℘゜4 小时前
前端实战:jQuery 多条件联合搜索(标题模糊查询 + 日历时间段筛选)
前端·javascript·jquery
武子康4 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
一点一木5 小时前
从60首歌到1个网站:输入你的故事,还你一首歌
前端·github
IT_陈寒6 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端