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

news2024/11/15 8:08:09

理解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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于xshell简答使用

xshell是一个远程工具下载 官网地址&#xff1a;https://www.xshell.com/zh/xshell/直接下载即可~选择免费的授权页面 下载 不然要收费 也不要用盗版。运行后的xshell界面我们要建立服务器的连接点击加号 新建连接 即可 输入连接地址后 要输入 账号 和 秘密 OK 好了 可以使用了…

【Linux】Linux编译器gcc、g++

文章目录&#x1f3aa; Linux编译器gcc、g&#x1f680;1. 程序的编译⭐1.1 预处理⭐1.2 编译⭐1.3 汇编⭐1.4 链接⭐1.5 gcc/g常用指令&#x1f680;2. 函数库⭐2.1 静态库⭐2.2 动态库⭐2.3 动静态库对比&#x1f3aa; Linux编译器gcc、g 我们这以gcc为例&#xff0c;g编译器…

【学习笔记之数据结构】树的认识

树的概念&#xff1a; 树是一种非线性的数据结构&#xff0c;它由n&#xff08;n可以为0&#xff09;个有限的节点组成一个具有层次关系的结合。之所以把它称之为树是因为它的逻辑结构形似一个倒着的树。它的根在上面&#xff0c;叶子在下面。   有一个特殊的节点&#xff0c…

WebRTC系列-Qos系列之接收NACK

文章目录 1. 主要调用流程1.1 RTCP和RTP包区分1.2 查找丢失包2. RTX在文章 WebRTC系列-Qos系列之RTP/RTCP源码分析-RTP/RTCP包解析-3的2.3.3章节介绍了NACK包在WebRTC中解析方式及RFC规定协议的定义。 这篇文章开始,详细分析其接受RTCP包到解析nack的处理流程; 1. 主要调用流…

Kotlin中空安全操作符,异常处理和自定义异常,以及先决条件函数详解

博主前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住也分享一下给大家 &#x1f449;点击跳转到教程 一、Kotlin的可空性 null 在java中我们司空见惯的空指针异常NullPointerException,带给了我们很多麻烦。 Kotlin作为更强…

STM32编写OLED显示屏驱动

文章目录前言一、OLED的器件地址二、编写写数据和写命令函数三、编写初始化OLED屏幕函数四、其他功能函数编写五、显示字符和数字函数编写总结前言 这篇文章将带大家学习如何编写OLED显示屏的驱动程序。这里我使用的是HAL库的硬件IIC&#xff0c;OLED屏幕使用的是SSD1306的。 …

sbt编程语言scala的构建工具配置及项目构建(附带网盘下载)

SBT简介 SBT 是 Scala 的构建工具&#xff0c;全称是 Simple Build Tool&#xff0c; 类似 Maven 或 Gradle。 Java可以用Maven快速构建项目&#xff0c;scala用SBT快速构建一个Scala项目。 sbt下载官网 百度网盘链接&#xff1a;https://pan.baidu.com/s/1eJkdWndZ0izcd3w…

Elasticsearch7.8.0版本高级查询——桶聚合查询文档

目录一、初始化文档数据二、桶聚合查询文档2.1、概述2.2、terms 聚合&#xff0c;分组统计的示例2.3、在 terms 分组下再进行聚合的示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求…

Yolo系列理论

参考文章&#xff1a;AI菌的YOLO系列 目标检测-Yolo系列发展Anchors Base原理Anchors Free原理YOLO v1Yolov1网络结构Yolov1实现方法Yolov1损失函数Yolov1总结YOLO v2Yolov2网络结构Yolov2改进细节Yolov2总结YOLO V3Yolov3网络结构YOLO V4Yolov4网络结构YOLOv4 BackBone训练策略…

数据库搏击

1. Mysql入门 1.1 Mysql5.7 安装配置 1.2 命令行连接到Mysql 1.3 图形化软件 1.3.1 Navicat SQLyog 1.4 数据库三层结构 所有安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database mana…

设计模式 - 六大设计原则之LSP(里氏替换)

文章目录概述里氏替换原则问题由来里氏替换的原则里氏替换原则的作用CaseBad ImplBetter Impl抽象银行卡类储蓄卡实现类信用卡实现类单元测试小结概述 里氏替换原则&#xff08;Liskov Substitution Principle , LSP&#xff09; 由麻省理工学院计算机科学西教授 Barbara Lisk…

MySQL两种引擎的索引

推荐看看这个视频&#xff0c;将两种引擎实现的索引原理说的比较清楚 4-2 索引概述 本文转载&#xff1a;想飞的盗版鱼的博客​​​​​​ mysql5.5之后都是用idb了 补充一个主键和索引的关系&#xff1a;Are You OK&#xff1f;主键就是聚集索引吗&#xff1f; 索引类型…

五个简单常用的系统命令

一、文件扫描修复 以管理员身份进入命令提示符&#xff0c;输入Sfc空格/scannow 二、使用Check disk命令检查并修复磁盘错误 1、开机时连续不断的点击F11键&#xff0c;选择&#xff08;疑难解答&#xff09;-选择&#xff08;高级选项&#xff09;-选择&#xff08;命令提示…

virtualbox运行Ubuntu系统

virtualbox安装 下载virtualbox VirtualBox 7.0.6版本下载 安装virtualbox 点击执行VirtualBox-7.0.6-155176-Win.exe即可&#xff0c; 直接点击"下一步" 直到 “完成” 导入以有的ova虚拟机文件 1&#xff09;点击导入 2&#xff09;选择下载的ova文件 3&a…

GD32F4——定时器(TIMERx)

一、概述 定时器的主要功能是用来计时&#xff0c;时间到达之后可以产生中断&#xff0c;提醒计时时间到&#xff0c;然后可以在中断函数中去执行功能。 GD32F450ZGT6共14个定时器&#xff0c;分别为 &#xff08;1&#xff09;高级定时器&#xff08;TIMERx&#xff0c;x0,…

LightGBM介绍

LightGBM介绍 GBDT是机器学习中一个长盛不衰的模型&#xff0c;其主要思想是利用弱分类器&#xff08;决策树&#xff09;迭代训练以得到最优模型&#xff0c;该模型具有训练效果好&#xff0c;不易过拟合等优点。GBDT不仅在工业界应用广泛&#xff0c;通常被应用于多分类、点…

【ONE·C || 数据存储】

总言 C语言&#xff1a;数据存储相关介绍。 文章目录总言1、基本数据类型介绍1.1、整体介绍1.2、各数据类型分别说明&#xff08;整型、浮点型、构造、指针、空&#xff09;2、整型在内存中的存储2.1、原码、反码、补码2.1.1、总体介绍2.1.2、char、short类型在内存中的存储范围…

顺序表的具体使用方法.数据解构(二)

前言 提示&#xff1a;文本为数据解构(一)后续补充文&#xff1a; 本文具体讲解顺序表的具体使用方法 提示&#xff1a;以下是本篇文 系列文章目录 第一章 数据解构(一) 文章目录 前言 系列文章目录 一、静态的使用方式 二、动态的使用方式 1.定义一个结构体 2.初始化 3.扩容…

【ArcGIS微课1000例】0060:ArcGIS打开las格式点云数据的方法

文章目录 1. 使用上下文菜单创建 LAS 数据集2. 使用地理处理工具创建 LAS 数据集3. 显示LAS数据集LAS 数据集是位于文件夹中的独立文件,并且引用 LAS 格式的激光雷达数据和用于定义表面特征的可选表面约束要素。ArcGIS中,可使用创建 LAS 数据集工具或 ArcCatalog 中文件夹的上…

23种设计模式(二十)——责任链模式【数据结构】

文章目录 意图什么时候使用责任链真实世界类比责任链模式的实现责任链模式的优缺点亦称:职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility 意图 将链中的每一个节点看作是一个对象,每个节点处理的请求不同,且内部自动维护一个下一节点对象。当一个请求…