React源码之render过程中发生了什么?

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

理解JSX

对于我们直接书写jsx语法我们的浏览器是不理解我们这种语法的所以需要babel来去转义那么可以通过plugin-transform-react-jsx来转译jsx语法使得浏览器可以识别我们的Jsx语法例如

<div>1<div>
转译过后
React.createElement("div",null,"1")

对于React.createElement实际是调用的这个方法来将element的属性收集并返回出一个对象

const ReactElement = function(type, key, ref, self, source, owner, props) {
  // 汇总起来将这个对象返回出去
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
};

上述是创建Element的方法那么在React中是通过isValidElement这个方法来判断这个Element是否合法的

function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

JSX与Fiber的关系

在首屏渲染的时候会创建workingInProgress树创建节点的依据就是组件返回的jsx对象而在更新时已经存在了一颗currentFiber树所以在生成workingInProgress树时会将组件返回的jsx对象与这个组件的current指针指向的节点做对比根据对比来生成workingInProgress Fiber这就是JSX与Fiber的关系

JSX会被编译成React.createElement的执行理论上来说只要改变了React.createElement方法就能改变页面最终的渲染结果

React Component 与React Element的关系

Component会作为React.createElement方法的第一个参数也就是type参数。

// 这个是一个Class Component
class A {
	render(){
    return 'A'
  }
}
// 这个是一个Function Component
function B(){

}

beginWork与completeWork过程中发生了什么

mount过程中起初currentFiber为空此时先根据jsx对象创建workInProgress Fiber创建完成后将workInProgress Fiber替换为current Fiber

可以打开浏览器的performance面板来查看React的渲染流程中都调用了什么

对于render阶段开始于renderRootSync方法对于commit阶段开始于commitRoot方法

在render阶段使用遍历实现了可以中断的递归其中递归可以分为递阶段与归阶段在递阶段执行的方法是benginWork在归阶段执行的方法为completeWork

打开控制台打开sources找到react-dom-development.js搜索beginWork与completeWork两个方法并打上断点刷新后可以看到当前的current对象中tag值为3

在ReactWorkTags文件中可以看到tag为3代表着HostRoot节点也就是当前应用的根节点

那么在后续的断点调试再调用benginWork的时候我们的current节点都为空这也就是之前在双缓存中所说的只有根节点存在current而其他节点只存在于workInProgress中

workInProgress节点的创建

对于Render阶段的会根据同步更新与异步更新来调用不同的方法作为入口对于同步更新与异步更新会调用performSyncWorkOnRoot/performConcurrentWorkOnRoot方法来作为入口接着会调用renderRootSync / renderRootConcurrent方法在方法内会调用createWorkInProgress方法来初次创建workInProgress FibercreateWorkInProgress方法源码如下

// 这里入参中的 current 传入的是现有树结构中的 rootFiber 对象
function createWorkInProgress(current, pendingProps) {
  var workInProgress = current.alternate;
  // ReactDOM.render 触发的首屏渲染将进入这个逻辑
  if (workInProgress === null) {
    // 这是需要你关注的第一个点workInProgress 是 createFiber 方法的返回值
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
    // 这是需要你关注的第二个点workInProgress 的 alternate 将指向 current
    workInProgress.alternate = current;
    // 这是需要你关注的第三个点current 的 alternate 将反过来指向 workInProgress
    current.alternate = workInProgress;
  } else {
    // else 的逻辑此处先不用关注
  }

  // 以下省略大量 workInProgress 对象的属性处理逻辑
  // 返回 workInProgress 节点
  return workInProgress;
}
 

创建完后接着会调用workLoop来循环创建workInProgress FiberworkLoop方法逻辑如下

// performSyncWorkOnRoot入口 会调用该方法
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}


// performConcurrentWorkOnRoot入口 会调用该方法
function workLoopConcurrent() {
  // 判断条件是否存在shouldYield的执行如果浏览器没有足够的时间那么会终止while循环
  // 也不会执行后面的performUnitOfWork函数自然也不会执行后面的render阶段和commit阶段
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

在performUnitOfWork中会开始调用beginWork/completeWork方法进行下一步Fiber的创建等等

什么是"递"与"归"

“递”

递阶段首先会从 rootFiber 开始向下深度优先遍历。遍历到的每个 Fiber 节点会调用 beginWork 方法并且该方法会为传入的 Fiber 节点创建它的子 Fiber 节点并赋值给 workInProgress.child 进行连接当遍历到叶子节点时就会进入归阶段这个过程也叫做调和

“归”

就是向上归并的过程会执行 completeWork 方法来处理 Fiber 节点当某个 Fiber 节点执行完 completeWork如果有兄弟 Fiber 节点会进入该兄弟节点的递阶段。如果不存在兄弟 Fiber 节点会进入父级节点的归阶段一直执行到 rootFiber 期间可以形成 effectList对于初始化构建会创建 DOM 对 DOM 事件收集、处理 style等

beginWork

beginWork源码如下删除了一些部分代码方便理解

function beginWork(current, workInProgress, renderLanes) {
  if (current !== null) {
    var oldProps = current.memoizedProps;
    var newProps = workInProgress.pendingProps;
  	// 来判断新旧props是否有变化以及是否有Context的变化
    // 并且判断workInProgress Fiber与current Fiber中的Dom节点的类型是否改变(比如div变为了span)
    // 如果改变了则进入如下分支
    if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
     workInProgress.type !== current.type )) {
      
      // 这个值主要来判断在更新中当前Fiber节点是否有变化
      didReceiveUpdate = true;
    } else {
			// props 和 context 没有发生变化检查是否更新来自自身或者 context 改变
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      
      if (
        !hasScheduledUpdateOrContext &&
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        didReceiveUpdate = false;
        // 这里会调用bailoutOnAlreadyFinishedWork的逻辑其实来去拦截不需要更新的节点
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;

    if (getIsHydrating() && isForkedChild(workInProgress)) {
     
      var slotIndex = workInProgress.index;
      var numberOfForks = getForksAtLevel();
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  }

  
	......
  // 这坨 switch 是 beginWork 中的核心逻辑原有的代码量相当大
  switch (workInProgress.tag) {
    ......
    // 这里省略掉大量形如"case: xxx"的逻辑
    // 根节点将进入这个逻辑
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes)
    // dom 标签对应的节点将进入这个逻辑
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes)

    // 文本节点将进入这个逻辑
    case HostText:
      return updateHostText(current, workInProgress)
    ...... 
    // 这里省略掉大量形如"case: xxx"的逻辑
  }
  // 这里是错误兜底处理 switch 匹配不上的情况
  {
    {
      throw Error(
        "Unknown unit of work tag (" +
          workInProgress.tag +
          "). This error is likely caused by a bug in React. Please file an issue."
      )
    }
  }
}

mount

在mounted过程中

对于benginWork当某一个节点进入benginWork时他的目的是为了创建当前Fiber节点的第一个子Fiber节点

对于这样的一个结构来讲在mounted过程中会先调用benginWork渲染根节点然后benginWork去渲染App节点接着benginWork渲染div然后是header再接着就是img可以理解为根据层级依次向下执行benginWork方法来构建Fiber Tree因为img无子节点所以此时会执行completeWork方法这就是一个递归的过程"递"阶段可以理解为是benginWork阶段对于归阶段可以理解为completeWork阶段。当img标签执行完completeWork方法后会判断是否有兄弟Fiber节点本次例子是有兄弟节点所以此时会执行兄弟节点p标签的benginWork阶段这个流程其实就是深度优先遍历。对于P节点也会有子节点Edit文本节点code节点则与上面的分析方法相同。总结下来是当有子节点时就深入没有子节点时就遍历兄弟节点也就是上面所说的深度优先遍历。

按照常理当执行到code节点生成Fiber时首先去执行benginWork接着子文本节点继续执行benginWork但是实际是code节点执行完benginWork后就直接会执行completeWork方法这是因为对于React中也做了一些优化比如对于code标签这种只有一个文本子节点的节点则这个文本节点不会生成自己的Fiber节点也就不会执行benginWork方法等等。

reconcileChildren

在reconcileChildren中主要通过current是否等于null来判断是首屏渲染还是更新的逻辑执行调用不同的函数最终的结果都是将生成的子Fiber节点赋值给workInProgress.child并作为本次beginWork的返回值并作为下次performUnitOfWork执行时workInProgress的传参。本次情况是mount的情况此时current为null则会调用mountChildFibers方法。

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  // 判断 current 是否为 null
  if (current === null) {
    // 若 current 为 null则进入 mountChildFibers 的逻辑
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
    // 若 current 不为 null则进入 reconcileChildFibers 的逻辑
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}
 
ChildReconciler

对于reconcileChildren方法内调用的两个方法实际是ChildReconciler传入不同的参数来返回的函数

const reconcileChildFibers = ChildReconciler(true);
const mountChildFibers = ChildReconciler(false);

在ChildReconciler封装了大量的函数来供reconcileChildFibers进行调用操作

function ChildReconciler(shouldTrackSideEffects) {
	// 删除节点的逻辑
  function deleteChild(returnFiber, childToDelete) {
    if (!shouldTrackSideEffects) {
      // Noop.
      return;
    } 
    // 以下执行删除逻辑
  }
  ......

  // 单个节点的插入逻辑
  function placeSingleChild(newFiber) {
    if (shouldTrackSideEffects && newFiber.alternate === null) {
      newFiber.flags = Placement;
    }
    return newFiber;
  }

  // 插入节点的逻辑
  function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex;
    if (!shouldTrackSideEffects) {
      // Noop.
      return lastPlacedIndex;
    }
    // 以下执行插入逻辑
  }
  ......
  // 此处省略一系列 updateXXX 的函数它们用于处理 Fiber 节点的更新

  // 处理不止一个子节点的情况
  function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
    ......
  }
  // 此处省略一堆 reconcileXXXXX 形式的函数它们负责处理具体的 reconcile 逻辑
  function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    // 这是一个逻辑分发器它读取入参后会经过一系列的条件判断调用上方所定义的负责具体节点操作的函数
  }

  // 将总的 reconcileChildFibers 函数返回
  return reconcileChildFibers;
}

对于shouldTrackSideEffects参数代表"是否需要追踪副作用"对于shouldTrackSideEffects的传参不同则执行不同的逻辑可以placeSingleChild为例子

function placeSingleChild(newFiber) {
  // 如果shouldTrackSideEffects为false也就是mountChildFibers的逻辑
  // 则不会挂载flags
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags = Placement;
  }
  return newFiber;
}

这里的flags其实也就是EffectTag对于EffectTag的类型如下

ReactFiberFlags

在ReactFiberFlags文件中可以看到会有非常多的节点标记类型那么在render阶段中我们会为需要操作Dom的Fiber节点打上标记这个也就是我们常说的EffectTag。

那么常见的类型主要有

// DOM需要插入到页面中
export const Placement = /*                */ 0b00000000000010;
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要删除
export const Deletion = /*                 */ 0b00000000001000;

比如Placement 这个 effectTag 的意义是在渲染器执行时也就是真实 DOM 渲染时告诉渲染器我这里需要新增 DOM 节点。 effectTag 记录的是副作用的类型而所谓“副作用”React 给出的定义是“数据获取、订阅或者修改 DOM”等动作。在这里Placement 对应的显然是 DOM 相关的副作用操作。

reconcileChildFibers

这个函数是一个逻辑分发器它将根据入参的不同执行不同的 Fiber 节点操作最终返回不同的目标 Fiber 节点。

createFiber

createFiber的作用来创建一个Fiber节点

createFiberFromElement通过ReactElement数据来创建一个Fiber节点

开始benginWork后的所有流程函数

非常不错的文章

https://juejin.cn/post/7122122884847403021#heading-9

https://juejin.cn/post/7040984743176830983

https://juejin.cn/post/7094917813936062471

https://juejin.cn/post/6967959680341508126#heading-7

https://juejin.cn/post/6844904202393616392#heading-2

update

benginWork的源码中主要是通过current是否等于null来判断是首次渲染还是更新的逻辑通过didReceiveUpdate来判断当前的更新是否源于父级的更新主要比较的源码如下

 if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    // 判断新旧props是否相等判断context是否发生了改变判断type是否发生了改变
    // context是指无需通过props就能在组件树之间数据传递的方法
    // type是指当前标签的类型
    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
    
      didReceiveUpdate = true;
    } else {
      // props和context没有发生变化来检测是否来自自身或者context发生改变
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );

      if (
        !hasScheduledUpdateOrContext &&
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        // No pending updates or context. Bail out now.
        didReceiveUpdate = false;

        // 这里会调用bailoutOnAlreadyFinishedWork的逻辑其实来去拦截不需要更新的节点
        // 在bailoutOnAlreadyFinishedWork方法中会调用cloneChildFibers来复制一份Fiber节点
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // This is a special case that only exists for legacy mode.
        // See https://github.com/facebook/react/pull/19216.
        didReceiveUpdate = true;
      } else {
        // An update was scheduled on this fiber, but there are no new props
        // nor legacy context. Set this to false. If an update queue or context
        // consumer produces a changed value, it will set this to true. Otherwise,
        // the component will assume the children have not changed and bail out.
        didReceiveUpdate = false;
      }
    }
  }
checkScheduledUpdateOrContext

检查当前 Fiber 节点上的 lanes 是否等于 updateLanes如果相等那么证明更新来源当前 fiber 返回 true

function checkScheduledUpdateOrContext(current, renderLanes) {
	
  var updateLanes = current.lanes;

  if (includesSomeLane(updateLanes, renderLanes)) {
    return true;
  } // No pending update, but because context is propagated lazily, we need

  return false;
}

当 checkScheduledUpdateOrContext函数返回 false则证明当前组件没有更新context 又没有变化只能是子节点更新。会进入 attemptEarlyBailoutIfNoScheduledUpdate 的逻辑在这个逻辑中会根据不同的 type 来复用 Fiber 节点

attemptEarlyBailoutIfNoScheduledUpdate

attemptEarlyBailoutIfNoScheduledUpdate这个函数会处理部分 Context 逻辑但是最重要的是调用了 bailoutOnAlreadyFinishedWork方法

function attemptEarlyBailoutIfNoScheduledUpdate(
  current: Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  switch (workInProgress.tag) {...}
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
 }
bailoutOnAlreadyFinishedWork

主要目的是来检查子树需要不需要更新首先通过 includesSomeLane来判断 childLanes 是否是高优先级的任务如果不是则子孙节点不需要被调和。
在includesSomeLane来判断当前 Fiber 节点的子孙节点中有没有需要在本次 render 过程中进行的更新任务如果没有则可以直接跳过当前节点下所有后代节点的 render
若后代节点中仍有本次 render 过程需要处理的更新任务则克隆 current 树上对应的子 Fiber 节点并返回作为下次 performUnitOfWork 的主体但组件本身不会 rerender

function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
  if (current !== null) {
    workInProgress.dependencies = current.dependencies;
  }

  {
    stopProfilerTimerIfRunning();
  }

  markSkippedUpdateLanes(workInProgress.lanes);
  // 如果 children 没有高优先级的任务说明所有的 child 没有更新那么child 不需要被调和
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    {
      return null;
    }
  } 

  // 当前fiber没有更新。但是它的children 需要更新
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}
cloneChildFibers

作用是复用current Fiber Tree上对应的子Fiber节点

function cloneChildFibers(current, workInProgress) {
  if (current !== null && workInProgress.child !== current.child) {
    throw new Error('Resuming work not yet implemented.');
  }

  if (workInProgress.child === null) {
    return;
  }

  var currentChild = workInProgress.child;
  var newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
  // 将新建的子Fiber节点与子节点建立联系
  workInProgress.child = newChild;
  newChild.return = workInProgress;

  // 遍历当前Fiber子节点的所有兄弟节点赋值给我们新创建的节点newChild来进行节点复用
  while (currentChild.sibling !== null) {
    currentChild = currentChild.sibling;
    newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps);
    newChild.return = workInProgress;
  }

  newChild.sibling = null;
} 

对于benginWork的update流程中最核心且主要的工作是bailoutOnAlreadyFinishedWork通过baiout来将与本次无关更新的Fiber树路径进行剪枝然后将其进行复用这种复用会保留本次剪枝中Fiber子树的所有子节点

completeWork

  • 根据workInProgress.tag进入不同函数我们以HostComponent举例
  • update时除了判断current=null外还需要判断workInProgress.stateNode=null调用updateHostComponent处理props包括onClick、style、children …并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上
  • mount时 调用createInstance创建dom将后代dom节点插入刚创建的dom中调用finalizeInitialChildren处理props和updateHostComponent处理的逻辑类似

mount

对于Dom节点的创建与更新主要在completeWork函数中大部分的创建都是会在HostComponent的情况中体现因此我们可以看一下HostComponent对应的部分

case HostComponent:{
    popHostContext(workInProgress);
    var rootContainerInstance = getRootHostContainer();
    var type = workInProgress.type;
  	// 首先判断current是否存在在首屏渲染时current是不存在的所以会进入else的逻辑
    if (current !== null && workInProgress.stateNode != null) {
      // 更新dom节点
      updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);

      if (current.ref !== workInProgress.ref) {
        markRef(workInProgress);
      }
    } else {
      // 首屏渲染的逻辑
      // 新增dom节点
      if (!newProps) {
        if (workInProgress.stateNode === null) {
          throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
        } 

        bubbleProperties(workInProgress);
        return null;
      }

      var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
    	// Hydrated是跟ssr相关的所以暂时不需要管
      var _wasHydrated = popHydrationState(workInProgress);

      if (_wasHydrated) {
        // 服务端渲染相关本次不关注
      } else { 
        // 在createInstance这个方法中主要用于创建对应的Dom节点
        var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
        // 当创建完Dom节点后插入到之前已经创建好的Dom树中这一步主要在appendAllChildren中执行
        appendAllChildren(instance, workInProgress, false, false);
        // 把创建的Dom节点保存在stateNode属性身上
        workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.

        // 为Dom节点设置Props属性
        if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
          markUpdate(workInProgress);
        }
      }

      if (workInProgress.ref !== null) {
        markRef$1(workInProgress);
      }
    }

    bubbleProperties(workInProgress);
    return null;
  }
createInstance

在这个方法中主要用于创建Dom节点核心还是通过createElement方法传入我们所定义的type、props等一些属性来创建Dom节点我们可以详细看一下对应的源码

function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
  var parentNamespace;

  {
    // TODO: take namespace into account when validating.
    var hostContextDev = hostContext;
    validateDOMNesting(type, null, hostContextDev.ancestorInfo);

    if (typeof props.children === 'string' || typeof props.children === 'number') {
      var string = '' + props.children;
      var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
      validateDOMNesting(null, string, ownAncestorInfo);
    }

    parentNamespace = hostContextDev.namespace;
  }
	// 通过createElement来创建一个Dom节点
  var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
  precacheFiberNode(internalInstanceHandle, domElement); 
  updateFiberProps(domElement, props);
  return domElement;
}
appendAllChildren

appendAllChildren的方法的作用是每次调用这个方法时都会将已经创建好的Dom节点挂载到当前父级的Dom节点下在completeWork依次向上"归"的过程就将当前的节点挂载到父节点上。

// Mutation mode
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
  // We only have the top Fiber that was created but we need recurse down its
  // children to find all the terminal nodes.
  var node = workInProgress.child;

  while (node !== null) {
    if (node.tag === HostComponent || node.tag === HostText) {
      appendInitialChild(parent, node.stateNode);
    } else if (node.tag === HostPortal) ; else if (node.child !== null) {
      node.child.return = node;
      node = node.child;
      continue;
    }

    if (node === workInProgress) {
      return;
    }

    while (node.sibling === null) {
      if (node.return === null || node.return === workInProgress) {
        return;
      }

      node = node.return;
    }

    node.sibling.return = node.return;
    node = node.sibling;
  }
finalizeInitialChildren
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
  setInitialProperties(domElement, type, props, rootContainerInstance);
	// 判断dom节点的类型如果带focus属性的Dom则返回props的autoFocus(布尔值)
  switch (type) {
    case 'button':
    case 'input':
    case 'select':
    case 'textarea':
      return !!props.autoFocus;

    case 'img':
      return true;

    default:
      return false;
  }
}
setInitialProperties

在这个函数中主要用于初始化props属性调用这个方法后又会调用setValueForProperty执行方法最后通过setAttribute来设置属性

function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
  // 首先判断是否是一个自定义的标签
  var isCustomComponentTag = isCustomComponent(tag, rawProps);

  {
    validatePropertiesInDevelopment(tag, rawProps);
  } // TODO: Make sure that we check isMounted before firing any of these events.
  
  var props;
	// 根据不同的标签执行添加不同的初始属性与监听器
  switch (tag) {
    case 'dialog':
      listenToNonDelegatedEvent('cancel', domElement);
      listenToNonDelegatedEvent('close', domElement);
      props = rawProps;
      break;

    case 'iframe':
    case 'object':
    case 'embed':
      // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the load event.
      listenToNonDelegatedEvent('load', domElement);
      props = rawProps;
      break;

    case 'video':
    case 'audio':
      // We listen to these events in case to ensure emulated bubble
      // listeners still fire for all the media events.
      for (var i = 0; i < mediaEventTypes.length; i++) {
        listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
      }

      props = rawProps;
      break;

    case 'source':
      // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the error event.
      listenToNonDelegatedEvent('error', domElement);
      props = rawProps;
      break;

    case 'img':
    case 'image':
    case 'link':
      // We listen to these events in case to ensure emulated bubble
      // listeners still fire for error and load events.
      listenToNonDelegatedEvent('error', domElement);
      listenToNonDelegatedEvent('load', domElement);
      props = rawProps;
      break;

    case 'details':
      // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the toggle event.
      listenToNonDelegatedEvent('toggle', domElement);
      props = rawProps;
      break;

    case 'input':
      initWrapperState(domElement, rawProps);
      props = getHostProps(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the invalid event.

      listenToNonDelegatedEvent('invalid', domElement);
      break;

    case 'option':
      validateProps(domElement, rawProps);
      props = rawProps;
      break;

    case 'select':
      initWrapperState$1(domElement, rawProps);
      props = getHostProps$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the invalid event.

      listenToNonDelegatedEvent('invalid', domElement);
      break;

    case 'textarea':
      initWrapperState$2(domElement, rawProps);
      props = getHostProps$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the invalid event.

      listenToNonDelegatedEvent('invalid', domElement);
      break;

    default:
      props = rawProps;
  }
  // 在这个函数中主要来判断我们的props是否合法
  assertValidProps(tag, props);
  // 合法的话我们就初始化对应的dom属性
  // 在这个方法中又会调用setValueForProperty方法来执行最后通过setAttribute来设置属性
  setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);

  switch (tag) {
    case 'input':
      // TODO: Make sure we check if this is still unmounted or do any clean
      // up necessary since we never stop tracking anymore.
      track(domElement);
      postMountWrapper(domElement, rawProps, false);
      break;

    case 'textarea':
      // TODO: Make sure we check if this is still unmounted or do any clean
      // up necessary since we never stop tracking anymore.
      track(domElement);
      postMountWrapper$3(domElement);
      break;

    case 'option':
      postMountWrapper$1(domElement, rawProps);
      break;

    case 'select':
      postMountWrapper$2(domElement, rawProps);
      break;

    default:
      if (typeof props.onClick === 'function') {
        // TODO: This cast may not be sound for SVG, MathML or custom elements.
        trapClickOnNonInteractiveElement(domElement);
      }

      break;
  }
} 

updated

当update时Fiber节点已经存在对应的DOM节点所以不需要生成DOM节点。需要做的主要是处理props。

case HostComponent:{
  popHostContext(workInProgress);
  var rootContainerInstance = getRootHostContainer();
  var type = workInProgress.type;
  // 首先判断current是否存在在更新阶段时current存在的所以通过此逻辑来判断是否为更新阶段
  if (current !== null && workInProgress.stateNode != null) {
    // 更新dom节点
    updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);

    if (current.ref !== workInProgress.ref) {
      markRef(workInProgress);
    }
  } 
}
updateHostComponent

通过此方法来对比新旧props返回一个需要更新属性名称的数组[key1,value1,key2,value2]并将其赋值给workInProgress.updateQueue至此render过程的update就执行完毕了

updateHostComponent = function(
  current: Fiber,
  workInProgress: Fiber,
  type: Type,
  newProps: Props,
) {
  
  const oldProps = current.memoizedProps;
  // 判断新旧props是否相同如果相同则直接不进行后面的逻辑
  if (oldProps === newProps) {
    return;
  }

  const instance: Instance = workInProgress.stateNode;
  const currentHostContext = getHostContext();

  // diff新旧props获取对应的变化
  const updatePayload = prepareUpdate(
    instance,
    type,
    oldProps,
    newProps,
    currentHostContext,
  );
  
  // 将处理完的props以数组的形式
  workInProgress.updateQueue = (updatePayload: any);
  if (updatePayload) {
    markUpdate(workInProgress);
  }
};

总结

React的更新任务主要是调用一个叫做workLoop的工作循环去构建workInProgress树构建过程分为两个阶段向下遍历和向上回溯向下和向上的过程中会对途径的每个节点进行beginWork和completeWork。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6