react源码学习

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码学习相关的知识,希望对你有一定的参考价值。

继上一篇react源码学习(1)

创建FiberRoot和rootFiber

//render调用方法
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // mount是null
  children: ReactNodeList, // ReactElement
  container: Container,  //容器
  forceHydrate: boolean,  
  callback: ?Function, //回叼
) 


  const maybeRoot = container._reactRootContainer;
  let root: FiberRoot;
  if (!maybeRoot) 
    // Initial mount //初始化mount
    root = legacyCreateRootFromDOMContainer(
      container,
      children,
      parentComponent,
      callback,
      forceHydrate,
    );
   else 
    // 第二次mount的时候
    root = maybeRoot;
    if (typeof callback === 'function') 
      const originalCallback = callback;
      callback = function() 
        const instance = getPublicRootInstance(root);
        originalCallback.call(instance);
      ;
    
    // Update
    updateContainer(children, root, parentComponent, callback);
  
  return getPublicRootInstance(root);


  • legacyRenderSubtreeIntoContainer是render最后调用的方法,他会判断当前容器是否有挂载属性,有的话就直接服用老的FiberRoot,然后调用updateContainer方法开启调度,比如wbepack热更新。
  • 对于第一次,会调用legacyCreateRootFromDOMContainer方法,创建FiberRoot
function legacyCreateRootFromDOMContainer(
  container: Container,
  initialChildren: ReactNodeList,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
  isHydrationContainer: boolean,
): FiberRoot 
  if (isHydrationContainer) 

   else 
    // First clear any existing content.
    let rootSibling;
    // 创建FiberRoot
    const root = createContainer(
      container,
      LegacyRoot,
      null, // hydrationCallbacks
      false, // isStrictMode
      false, // concurrentUpdatesByDefaultOverride,
      '', // identifierPrefix
      noopOnRecoverableError, // onRecoverableError
      null, // transitionCallbacks
    );
    
    // 容器上挂载root
    container._reactRootContainer = root;

    // Initial mount should not be batched.
    // 初始化渲染优先级比较高,不应该不应分批处理。
    flushSync(() => 
      // 进入调度阶段了
      updateContainer(initialChildren, root, parentComponent, callback);
    );

    return root;
  

可以看到这个方法就是创建FiberRoot然后挂载到容器上,最后调用flushSync调用updateContainer调度。flushSync表示以同步到方式执行updateContainer。因为初始化渲染优先级比较高。
那么createContainer就是用来创建FiberRoot和rootFiber的
createConinter会调用createFiberRoot

export function createFiberRoot(
  containerInfo: any,
  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 
  // FiberRoot
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
  ): any);
  
  // RootFiber
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );

  //FiberRoot和rooFiber联系
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;


    const initialState: RootState = 
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
      transitions: null,
    ;
// rootFiber的初始化state
  uninitializedFiber.memoizedState = initialState;
  

  // 初始化RootFiber的updateQueue
  initializeUpdateQueue(uninitializedFiber);

  return root;


createFiberRoot主要做了

  • 1 创建FIberRoot和rootFiber并且联系起来
  • 2 初始化rootFiber的state
  • 3 初始化rootFiber的updateQueue

创建任务对象并存储于任务队列

上面说到updateContainer会开启调度。
它会创建一个update任务,插入到rootFiber上,并且开启调度。

export function updateContainer(
 element: ReactNodeList, // <App/>
  container: OpaqueRoot,  // FiberRoot
  parentComponent: ?React$Component<any, any>, // null
  callback: ?Function,  )
// current就是RootFiber
  const current = container.current;
  // 获取当前react应用初始化的时间
  const eventTime = requestEventTime();
  // 获取优先级
  const lane = requestUpdateLane(current);
  
// 创建update
  const update = createUpdate(eventTime, lane);
  // 对于HostRoot,update的payload就是element
  update.payload = element;
  
 // 将update插入fiber.updateQueue上
  enqueueUpdate(current, update, lane);
  // 开启调度
  const root = scheduleUpdateOnFiber(current, lane, eventTime);
  if (root !== null) 
    entangleTransitions(root, current, lane);
  
  return lane;

  • 创建update
  • 插入到fiber.updateQueue.shard.pending上
  • 调用scheduleUpdateOnFiber开启调度

执行任务前的准备工作

上面已经创建了update,放入了任务队列之中,现在应该来执行任务了。updateConitnaer调用了schedlueUpdateOnFiber来开启调度。
这个方法

// 创建Update之后,就需要开启调度更新了。
// 做的事情:
// 1: 通过markUpdateLaneFromFiberToRoot找到rootFiber
// 2: 找到rootFiber之后,调用ensureRootIsScheduled开始调度
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number
)

  /**
   * 判断是否hi无限循环的update,如果是就报错。
   * 比如在componentWillUpdate或者componentDidupdate生命周期中重复调用setState方法,就会发生这种情况。
   * react限制了嵌套更新的数量防止无限制更新,限制的嵌套数量是50
   */
  checkForNestedUpdates();

  // 遍历找到rootFiber
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) 
    return null;
  
   // 开始调度
  ensureRootIsScheduled(root, eventTime);

主要做了两件事情

  • 通过fiber找到rootFiber
  • 调用ensureRootIsScheduled开始调度
ensureRootIsScheduled

顾名思义,确保root正在调度。
ensureRootIsScheduled这个方法是关键。他是react自己实现的一个优先级的调度函数。

  • 判断当前调度任务的优先级以及是否有正在调度的任务,有就判断两者优先级,优先级相同,则不做处理,优先级不同,打断当前的调度,开启新的调度,优先执行优先级高的任务
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) 
  //正在工作的任务
  const existingCallbackNode = root.callbackNode;
 //当前调度的任务的优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );
   // 如果当前调度的任务优先级是NoLanes,不需要调度,直接刷新全局变量,并且取消当前的工作的任务
  if (nextLanes === NoLanes) 
    // Special case: There's nothing to work on.
    if (existingCallbackNode !== null) 
      cancelCallback(existingCallbackNode);
    
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  
 // 获取此次任务的Priority
  const newCallbackPriority = getHighestPriorityLane(nextLanes);
  // 获取当前正在执行的任务的优先级
  const existingCallbackPriority = root.callbackPriority;
  if ( existingCallbackPriority === newCallbackPriority)
	return;

 //如果不一样,并且存在正在工作的任务,取消当前正在工作的任务
  if (existingCallbackNode != null) 
    // Cancel the existing callback. We'll schedule a new one below.
    cancelCallback(existingCallbackNode);
  

   
  • 第二则是通过判断当前调度任务的优先级,同步的话调用performSyncWorkOnRoot,异步的话调用performConcurrentWorkOnRoot。
  // 调度一个新的任务
  let newCallbackNode;

  // 判断当前调度的任务是同步还是异步
  if (newCallbackPriority === SyncLane) 
	scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
	else 
	 // 异步调度,scheduleCallback的返回值就是当前注册的任务newTask
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );

 root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
	

走到这里表示了当前调度的任务是最高优先级的,所以,他会把当前最新的任务重新挂载在root上,注意,这个跟另一个函数performConcurrentWorkOnRoot配合,实现react自己任务优先级的调度。

  • 我们现在只需要了解ensureRootIsSchedule主要就是用来判断当前是否有更高优先级的任务,有的话就停止当前任务,创建新的调度

构建workInprogress Fiber树中的rootFiber

当面说到执行performConcurrentWorkOnRoot函数,他是render阶段的入口。
performConcurrentWorkOnRoot会根据当前任务是否过期,决定调用renderRootConcurrent或者renderRootSync,这两个函数会返回一个状态,performConcurrentWorkOnRoot根据这个状态来决定是否进入commit阶段。还是开启新的一轮调度。

function performConcurrentWorkOnRoot(root, didTimeout) 
let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);


因为performConcurretnWorkOnRoot是通过Scheduler调度的,所以她会接受一个didTimeout参数,表示当前帧是否有剩余时间。
重点看下renderRootConcurrent
renderRootConcurrent会创建workInprogress fiber树的rootFiber,
然后调用workLoopConcurrent真正去执行任务。

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) 
// 当wokrInprogress不等于root,就要创建workInprogress
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) 
 // 构建workInporgressFiber 树及rootFiber
    prepareFreshStack(root, lanes);


  do 
    try 
      // 真正执行任务
      workLoopConcurrent();
      break;
     catch (thrownValue) 
      handleError(root, thrownValue);
    
   while (true);

prepareFreshStack用于创建workInprogress Fiber的rootFiber。

function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber 
  root.finishedWork = null; // finishWork标识render阶段完成后构建待提交的对象
  root.finishedLanes = NoLanes; //初始化优先级
  if (workInProgress !== null) 
    let interruptedWork = workInProgress.return;
    while (interruptedWork !== null) 
      const current = interruptedWork.alternate;
      unwindInterruptedWork(
        current,
        interruptedWork,
        workInProgressRootRenderLanes
      );
      interruptedWork = interruptedWork.return;
    
  
 
  // 构建workInprogress的FiberRoot
  workInProgressRoot = root;
  // 构建rootFiber
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
  workInProgressRootRenderLanes =
    subtreeRenderLanes =
    workInProgressRootIncludedLanes =
      lanes;
  workInProgressRootExitStatus = RootInProgress;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootInterleavedUpdatedLanes = NoLanes;
  workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
  workInProgressRootConcurrentErrors = null;
  workInProgressRootRecoverableErrors = null;

  return rootWorkInProgress;

可以看到,调用createWorkInProgress就是来创建workInprogress fiber树的rootFiber。

workLoopConcurrent方法解析

上面说到,workLoopConcurrent是真正执行任务的方法,

function workLoopConcurrent() 
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) 
    performUnitOfWork(workInProgress);
  

  • 我们刚刚才创建了workInprogress fiber的rootFiber赋值给workInProgress ,shouldYild是判断当前帧是否还有多余时间让他执行performUnitOfWork。
  • react16可以中断的最小粒度就是fiber,每一帧至少会执行一个fiebr的调度。当shouldYield返回true的时候,表示该中断了,得把线权交给浏览器了。
  • 然后退出循环。renderRootConcurrent会返回一个退出状态给performConcurrentWorkOnRoot,performConcurrentWorkOnRoot会决定要不要进入commit阶段。
  • 如果任务还没结束,performConcurrentWorkOnRoot不会进入commit阶段,反而会继续调用ensureRootIsScheduled方法,就是我们前面说的可以来判断当前是否有更高优先级的任务,如果没有,那么performConcurrentWorkOnRoot就会继续返回当前的任务。而schedulerCallback执行的时候会以当前任务的返回值决定该任务是否需要继续调度,需要的话就下一帧继续执行。

performUnitOfWork解析

上面说到。workLoopConcurrent会调用performUnitOfWork去调度fiber。那么performUnitOfWork是怎么处理fiber的呢?

function performUnitOfWork(unitOfWork: Fiber): void 
  const current = unitOfWork.alternate;

  let next;
  next = beginWork(current, unitOfWork, subtreeRenderLanes);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  
  if (next === null) 
    // 进入归阶段
    completeUnitOfWork(unitOfWork);
   else 
    workInProgress = next;
  

  ReactCurrentOwner.current = null;

  • 参数就是当前调度的fiber,对于第一次,这就是workInprogress fiber的rootFiber。
  • beginWork,递阶段,处理fiber,创建子fiber。从父到子
  • 如果next === null,调用completeUnitOfWork,归阶段,从子到父。
  • react16通过循环来模拟15的递归处理阶段。beginWork就像是递阶段,completeUnitOfWork就像是归阶段。但他们是可以中断的。

当所有fiber处理完成之后,表示render阶段完成,要进入commit阶段了。

render阶段进入commit阶段。

workLoopConcurrent执行完毕之后,会判断workInProgress是否等于null。如果等于null,表示Reconciler工作完成,要开启commit阶段了。

function renderRootConcurrent(root: FiberRoot, lanes: Lanes)
  // 构建workInporgressFiber 树及rootFiber
    prepareFreshStack(root, lanes);

	 do 
    try 
      // 真正执行任务
      workLoopConcurrent();
      break;
     catch (thrownValue) 
      handleError(root, thrownValue);
    
   while (true);
  
// Check if the tree has completed.
// 判断是否render完毕
  if (workInProgress !== null)
    return RootInProgress;
   else 
    // Set this to null to indicate there's no in-progress render.
    workInProgressRoot = null;
    workInProgressRootRenderLanes = NoLanes;

    // Return the final exit status.
    return workInProgressRootExitStatus;
  

如上,renderRootConcurrent会返回一个状态,RooInprogress就表示当前还在调度之中,而当workInprogress为null,就返回workInProgressRootExitStatus退出的状态。
而我们知道performConcurrentWorkOnRoot会根据renderRootConcurrent返回的状态决定是否进入commit阶段

 function performConcurrentWorkOnRoot(root, didTimeout)

	let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
 if (exitStatus !== RootInProgress) 
	...
	 // 将构建好的rootfiber存储到FiberRoot上
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;
	 //完成了render阶段之后,开启commit阶段
      finishConcurrentRender(root, exitStatus, lanes);



  // 每次执行完performConcurrentWorkOnRoot都会调用ensureRootIsScheduled来判断当前是否有更高优先级的任务需要调度
  ensureRootIsScheduled(root, now());
  //如果没有更高优先级或者当前任务就是最高优先级的,继续返回该任务
  if (root.callbackNode === originalCallbackNode) 
    // performConcurrentWorkOnRoot是ScheduleCallback注册的函数,而ScheduleCallback执行的时候,需要通过返回来确定该任务是否继续执行
    // 这里通过ensureRootIsScheduled调度之后,发现root上面挂载的任务还是当前这个任务,表示当前的任务依然是最高优先级的。
    // 所以,需要返回当前的任务给ScheduleCallback,以表示当前任务依然是最高优先级,需要执行。
    return performConcurrentWorkOnRoot.bind(null, root);
  
  //当调用ensureRootIsScheduled调度之后,如果有更高优先级的,或者任务都执行完毕了,那么这里返回null给scheduleCallback
  // 表示当前任务已经结束,当Schedule执行注册的函数performConcurrentWorkOnRoot,结果是Null的时候,他会认为该任务已经结束。
  // 会将该任务从最小堆中取出,然后继续调度,看有没有更高优先级的任务,注意,Schedule和React里面有各自的调度系统
  return null;

如上,performConcurrentWorkOnRoot,在确定退出状态是结束状态的时候,就会调用finishConcurrentRender,顾名思义,已经完成了并发的render工作。
而finishConcuurentRender的工作就是

function finishConcurrentRender(root, exitStatus, lanes) 
  switch (exitStatus) 
    case RootInProgress:
    case RootFatalErrored: 
      throw new Error("Root did not complete. This is a bug in React.");
    
    ....
    case RootCompleted: 
      // The work completed. Ready to commit.
      commitRoot(root, workInProgressRootRecoverableErrors);
      break;
    
    default: 
      throw new Error("Unknown root exit status.");
    
  

根据传入的状态,调用commitRoot函数,开启commit阶段,commit阶段是不可中断的。

commitRoot

commitRoot是commit阶段的入口

function commitRoot(root: FiberRoot, recoverableErrors: null | Array<mixed>) 
  const previousUpdateLanePriority = getCurrentUpdatePriority(); // 获取当前优先级保存
  const prevTransition = ReactCurrentBatchConfig.transition;

  try 
    ReactCurrentBatchConfig.transition = null;
    setCurrentUpdatePriority(DiscreteEventPriority); // 修改当前优先级
    // 因为commit阶段是不可被打断的,所以优先级绝对是最高的
    commitRootImpl(root, recoverableErrors, previousUpdateLanePriority);
   finally 
    ReactCurrentBatchConfig.transition = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority); //恢复之前的优先级
  

  return null;

因为commit阶段是不可被打断的,所以执行commit阶段之前,优先级要设为最高优先级。

commitRootImpl

commitRootImpl是commit阶段主要执行的函数,来看看他做了什么

function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  renderPriorityLevel: EventPriority
) 

 // --------before-mutation-之前的阶段-start-------
 const finishedWork = root.finishedWork; // rootFiber
  const lanes = root.finishedLanes; //优先级
   // 重置FiberRoot的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes

以上是关于react源码学习的主要内容,如果未能解决你的问题,请参考以下文章

React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

第十七篇:特别的事件系统:React 事件与 DOM 事件有何不同?

第十七篇:特别的事件系统:React 事件与 DOM 事件有何不同?

第十七篇:特别的事件系统:React 事件与 DOM 事件有何不同?

react源码学习

react源码学习