React源码入门篇(一)

  • 最近比较空闲, 以前对React的了解总是有限, 故打算最近把源码给看了
  • 阅读代码的思路会先直接通过打断点+github拉取源码的方式综合阅读, 从头开始把React整个执行链路大概搞清楚, 这个方面会分成四个部分进行
  • 后面看情况,根据模块细分然后再细看源码

源码和运行代码都是使用的React18.2.0

一、总览

react拉下的目录有很多个, 我们主要关注的是packages目录, 其中又有一些比较核心的包, 我们找到的关键代码基本都来自以下包, 例如

sh 复制代码
 |-- react # 一些核心通用的API例如useState, useRef
 |-- react-dom  # dom相关的源码
 |-- react-recociler # Fiber相关代码
 |-- schduler # 调度相关代码

先来个简单的代码,然后直接开启调试。 以index.js作为入口, 其中又调用了createRoot方法, 传入了#rootDOM对象, 然后返回一个root对象, 再调用render方法

jsx 复制代码
// index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// App.js
import { useState } from 'react';
import { useState } from 'react';
function App() {
  const [count, setCount] = useState();
  const changeCount = () => {
    setCount(count => count + 1);
  }
  return (
    <div>
      <div onClick={changeCount}>{count}</div>
    </div>
  );
}

export default App;

二、 找到入口

我们先从ReactDOM.createRoot()方法入手, 先来探究他做了些什么事情

  • 从代码中可以看到他就是直接调用了createRootImpl
js 复制代码
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  ....
 return createRootImpl(container, options);
}
  • 再看createRootImpl, 先通过nodeType判断了传入的是否为有效DOM, 然后根据optionscontainer生成对应的FiberRootNode应用根对象, 然后再在#Root上监听了所有事件, 最后根据FiberRootNode创建了一个ReactDOMRoot返回
js 复制代码
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
    // 通过nodeType判断是否为有效DOM元素
  if (!isValidContainer(container)) {
    throw new Error('createRoot(...): Target container is not a DOM element.');
  }
  ...
  // 创建对的FiberRootNode
  // 其内部调用了createFiberRoot->createHostRootFiber->createFiber
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  // 内部通过node[internalContainerInstanceKey] = hostRoot;进行标记
  // 而内部又定义了const internalContainerInstanceKey = '__reactContainer$' + randomKey;
  // 故就是给#root这个DOM元素设置了一个内部的属性, 存储了生成的FiberRootNode的current对象
  markContainerAsRoot(root.current, container);
  Dispatcher.current = ReactDOMClientDispatcher;

  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  // 监听所有事件   
  listenToAllSupportedEvents(rootContainerElement);

  // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
  return new ReactDOMRoot(root);
}
  • 我们可以再细看一下createContainer调用链路, 毕竟是整个Fiber结构的开始
  • 逻辑上也很清晰
    • 首先生成了FiberRootuninitializedFiber(也就是hostFiberRoot
    • 然后让彼此关联起来(通过currentstateNode指针)
    • 再对uninitializedFiber上的memoizedStateupdateQueue进行初始化

生成的这两玩意都挺重要, 后面多次使用

js 复制代码
export function createFiberRoot(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
  // 创建FiberRootNode应用根节点
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
  ): any);
    ....
  // 创建hostRootFiber, Fiber树的根节点
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  // 将两者关联
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;
  .....
  // 初始化hostRootFiber上的memoizedState
  const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
  };
  uninitializedFiber.memoizedState = initialState;
  // 初始化hostRootFiber的updateQueue属性
  initializeUpdateQueue(uninitializedFiber);
  // 返回Root
  return root;
}
  • 调用new FiberRootNode拿到的Root根应用对象, 其中有一些属性还是值得了解一下, 在后面都会接触到

    • current: 指向当前的Fiber
    • containerInfo: 指传入的DOM元素
    • pendingLanes: 待更新任务优先级
    • suspendedLanes:挂起任务优先级
    • pingedLanes: 需要重新调度的更新任务优先级
    • evnetTime: 任务启动时间数组, 初始化每一项都为0
    • expirationTimes: 任务过期时间数组, 初始化每一项都为-1
  • 调用createHostRootFiber内部又是调用了createFiber -> new FiberNode , 其实就是生成了一个Fiber节点, 一些关键的属性也值得了解一下

    • tag: 节点类型
    • key: 节点Key
    • return: 指向父节点
    • sibling: 指向下一个兄弟节点
    • child:指向第一个子节点
    • pendingProps: 等待处理的props
    • memoizedProps: 存储的props
    • updateQueue: 存储更新对象链表
    • memoizedState: 存储hook链表
    • mode: 模型
    • lanes: 优先级调度
    • childLanes: 子节点的优先级
    • dependencies: 保存context, 事件相关内容
    • flags: 副作用标记
  • 以上描述的逻辑如图所示

三、启动

接着我们可以从调用栈入手, 可以看到加载完src/index.js之后, 调用render函数-> updateContainer函数-> scheduleUpdateOnFiber函数 -> ensureRootIsScheduled函数 -> schedulePerformWorkUntilDeadline函数-> performWorkUntilDeadline -> flushWork函数 -> workLoop函数 -> renderRootSync函数 -> workLoopSync函数 -> performUnitOfWork函数 -> beginWork函数-> ...................

我们将上述的函数(以及一些这里还没有走到的函数)分成四个部分分别分析一通, 接下去的几篇文章的目录也是基于这个展开。 可供快速定位

  • 入口初始化过程
    • render()
    • updateContainer()
  • Scheduler: 根据优先级以及时间分片对任务进行调度
    • scheduleUpdateOnFiber()
    • ensureRootIsScheduled()
    • unstable_scheduleCallback()
    • requestHostCallback()
    • schedulePerformWorkUntilDeadline()
    • performWorkUntilDeadline()
    • flushWork()
    • workLoop()
    • performConcurrentWorkOnRoot()
  • Reconciler: FiberTree的构建生成
    • renderRootSync()
    • workLoopSync()
    • performUnitOfWork()
    • beiginWork()
    • uppateHostRoot()
    • reconcileChildFibers()
    • completeUnitOfWork()
    • completeWork()
  • Commit: 根据Reconciler阶段创建的Fiber树,构建真实的DOM树
    • finistConcurrentRender()
    • commitRootImpl()
    • flushPassiveEffects()
    • commitBeforeMutationEffects()
    • commitMutationEffects()
    • commitLayoutEffects()

这一篇,我们先把初始化过程给搞定

四、入口初始化

这个过程主要就两个函数, 一个是作为入口的rende()函数, 一个是updateContainer()函数

render函数

  • 从上面的createRoot()函数我们可以知道, 最后返回的是ReactDOMRoot的实例, 且在实例化的时候我们只实例了一个属性,也就是_internalRoot。 那么我们的render方法自然是通过prototype挂载在原型链上
  • 这里只对一些情况做报错处理,然后内部直接调用了updateContainer
js 复制代码
ReactDOMRoot.prototype.render =
  // $FlowFixMe[missing-this-annot]
  function (children: ReactNodeList): void {
    const root = this._internalRoot;  // 拿到我们生成的RootFiberNode
    if (root === null) {
      throw new Error('Cannot update an unmounted root.');
    }
    const container = root.containerInfo;  // 拿到传入的DOM元素
    ....
    updateContainer(children, root, null, null);
  };

updateContainer函数

  1. 先来结论
  • 获取当前Fiber节点的优先级lane
  • 根据eventTimelane创建update对象
  • update对象合并入Fiber的updateQueue
  • 然后调用scheduleUpdateOnFiber调度更新Fiber
  1. 逐步分析
  • 整体代码如下, 可以分别对照上述结论
js 复制代码
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  // container值得是FiberRootNode, current拿出其中的HostFiber
  const current = container.current;
  // 获取当前时间
  const eventTime = requestEventTime();
  // 获取到当前Fiber优先级lane
  const lane = requestUpdateLane(current);
  ...
  // 获取父组件的上下文
  const context = getContextForSubtree(parentComponent);
  // 处理上下文
  if (container.context === null) {
    container.context = context;  // 继承上下文
  } else {
    container.pendingContext = context; // 记录等待上下文
  }
  // 创建更新对象
  const update = createUpdate(eventTime, lane);
  // 设置payload
  update.payload = { element };
  if (callback !== null) {
    .... 
    update.callback = callback;  // 初始化callback
  }
  ....
  //将更新对象update推入栈中
  const root = enqueueUpdate(current, update, lane);
  if (root !== null) {
    // 开启调度任务
    scheduleUpdateOnFiber(root, current, lane, eventTime);
    entangleTransitions(root, current, lane);
  }
  return lane;
}

其中也有比较重要的执行过程, 其中涉及到优先级相关概念, 故我们先了解一波优先级,然后再回看该函数

五、优先级

优先级的出现就肯定有高低的比较, 目的是让高优先级的任务可以先执行, 低优先级的任务延后执行。 在React中我们常用到三种优先级。

优先级分类

  • lane优先级, 一共有31种优先级
js 复制代码
export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000010000;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;
  • event优先级,分为四种, 分别指向不同的优先级
js 复制代码
// 离散事件优先级, 为同步优先级0b0000000000000000000000000000010
// 比如click,input, change, blur, focus等
export const DiscreteEventPriority: EventPriority = SyncLane;
// 连续事件优先级, 为输入持续优先级0b0000000000000000000000000001000
// 比如touchmove, scroll,dragenter等
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认事件优先级, 为0b0000000000000000000000000100000
export const DefaultEventPriority: EventPriority = DefaultLane;
// 空闲事件优先级, 为0b0100000000000000000000000000000
export const IdleEventPriority: EventPriority = IdleLane;
  • scdeluar优先级
js 复制代码
  let ReactPriorityLevels: ReactPriorityLevelsType = {
    ImmediatePriority: 99,
    UserBlockingPriority: 98,
    NormalPriority: 97,
    LowPriority: 96,
    IdlePriority: 95,
    NoPriority: 90,
  };

优先级转换

  • lane优先级转为event优先级, 通过lanesToEventPriority
  • event优先级转换为scheduler优先级, 具体逻辑在ensureRootIsScheduled, 后面会涉及对该函数的讲解

优先级的操作

优先级的各种操作都是通过位运算进行的

  • 合并

    • 当遇到多个优先级时,往往需要合并到lanes进行处理
    • 采用的是位获计算。
    • 在很多地方都能看到该用法, 例如(在后面都会涉及)
      • markRootUpdated更新pendingLanesroot.pendingLanes |= updateLane;
      • markStarvedLanesAsExpired更新expiredLanes: root.expiredLanes |= lane;
    js 复制代码
    01000 | 00100 = 01100
  • 释放

    • 当执行完或者计算完需要释放相关优先级时,此时也需要对lanes进行处理
    • 采用的是位与和位非
    • 例如:
      • markStarvedLanesAsExpired处理lanes的时候, 当前lanes处理完之后就会采用lanes &= ~lane;释放, 以便下一个循环能够拿到下一个优先级
    js 复制代码
    11000 & ~10000 -> 11000 & 01111 = 01000
  • 拿到最高优先级

    • 这个React直接写了一个函数
    • 采用的是位与和符号位取反
    js 复制代码
    export function getHighestPriorityLane(lanes: Lanes): Lane {
      return lanes & -lanes;
    }
    • 使用到他的地方就很多了, 包括上面讲述的
      • laneevent优先级的转换
      • ensureRootIsScheduled用它找出最高级lane来表示回调函数的优先级
    js 复制代码
    10001 & -10001 = 00001

六、 updateContainer

了解完优先级我们继续回到updateContainer

requestUpdateLane

  • updateContainer中通过调用requestUpdateLane拿到优先级
  • requestUpdateLane代码如下
js 复制代码
export function requestUpdateLane(fiber: Fiber): Lane { 
// 获取到当前渲染的模式
const mode = fiber.mode; 
// 通过按位与的方式判断是否并发模式
if ((mode & ConcurrentMode) === NoMode) { 
    return (SyncLane: Lane);  // 非并发的话优先级都为同步优先级
    // workInProgressRootRenderLanes是需要更新的fiber节点上的lane的值
    // 不为空的时候则说明当前有任务在执行
} else if (
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes ) {
   // 这里又调用了getHighestPriorityLane,上面我们描述过;了,就是为了找最高优先级
   return pickArbitraryLane(workInProgressRootRenderLanes);
} 

// 检查当前事件是否是过渡优先级 
  const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
    .......
    // 拿到当前的过渡任务优先级,然后左移一位,过渡优先级一共有16种, 如果16种都用完了就重开
    const actionScopeLane = peekEntangledActionLane();
    return actionScopeLane !== NoLane  ? : requestTransitionLane();
  }
  // 此次更新的优先级 
  const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if (updateLane !== NoLane) {
    return updateLane;
  }
  // 此时事件的优先级
  // 内部使用了switch case对不同的event获取不同的优先级, 都没命中的话则走到
  // default为默认优先级16
  const eventLane: Lane = (getCurrentEventPriority(): any);
  return eventLane;
}

createUpdate

  • updateContainer中, update对象是通过createUpdate()创建的
js 复制代码
export function createUpdate(lane: Lane): Update<mixed> {
  const update: Update<mixed> = {
    lane, // 优先级
    tag: UpdateState,  // 0 
    payload: null,  // 更新内容, updateContainer还会进行赋值操作
    callback: null, // 回调,updateContainer还会进行赋值操作
    next: null,  // 通过next指向下一个update对象形成链表
  };
  return update;
}

enqueueUpdate

  • updateContainerupdate对象是通过enqueueUpdate()加入更新队列的, 其中又调用了enqueueConcurrentClassUpdate
js 复制代码
 export function enqueueUpdate<State>(
   fiber: Fiber,
   update: Update<State>,
   lane: Lane,
 ): FiberRoot | null {
   const updateQueue = fiber.updateQueue;
   if (updateQueue === null) {
     // Only occurs if the fiber has been unmounted.
     return null;
   }
   // 拿到Fiber.updateQueue.shared
   const sharedQueue: SharedQueue<State> = (updateQueue:  any).shared;
 // 判断是否为不安全渲染
 if (isUnsafeClassRenderPhaseUpdate(fiber)) {
  // 涉及类组件的UNSAFE_componentWillReceive才会走到这
   const pending = sharedQueue.pending;
   if (pending === null) {  // 开头
     update.next = update;
   } else {
     update.next = pending.next;
     pending.next = update;
   }
   sharedQueue.pending = update;
   return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
 } else {
  // 基本上都是走这个分支
   return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
   }
 }
  • 不安全渲染模式的更新如图所示

enqueueConcurrentClassUpdate

  • 基本上都是记录当前update的相关信息, 推入全局的concurrentQueue中.
js 复制代码
export function enqueueConcurrentClassUpdate<State>(
  fiber: Fiber,
  queue: ClassQueue<State>,
  update: ClassUpdate<State>,
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
  return getRootForUpdatedFiber(fiber);
}
// concurrentQueuesIndex是全局维护的一个下标, concurrentQueues是全局维护的栈
let concurrentQueuesIndex = 0;
const concurrentQueues: Array<any> = [];
let concurrentlyUpdatedLanes: Lanes = NoLanes;
function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
  // 下标做递增操作, 然后把相关的数据放入全局栈中
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;
  // 做合并操作
  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
  // 更新fiber上的lanes
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}

这一部分需要了解的东西很少,主要是初步对源码进行了划分、然后了解了相关的基本概念。

处理完更新对象之后, 就走到了scheduleUpdateOnFiber(root, current, lane);, 进入了我们的第二阶段, 这部分内容放在第二篇文章进行讲解

相关推荐
GIS程序媛—椰子5 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00111 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端14 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100918 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤439128 分钟前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt