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

理解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 Fiber,createWorkInProgress方法源码如下:

// 这里入参中的 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 Fiber,workLoop方法逻辑如下:

// 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后的所有流程函数:

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。

猜你喜欢

转载自blog.csdn.net/liu19721018/article/details/128753245