reconcileChildren的解读

简介: reconcileChildrendom diff的入口函数就是reconcileChildren,那么他的源码如下://packages/react-reconciler/src/ReactFiberBeginWork.old.jsexport function reconcileChildren( current: Fiber | null,//当前的fiber节点 workInProgress: Fiber,// 新生成的fiber nextChildren: any,// 新生成的reactElement内容

reconcileChildren
dom diff的入口函数就是reconcileChildren,那么他的源码如下:
//packages/react-reconciler/src/ReactFiberBeginWork.old.js
export function reconcileChildren(
current: Fiber | null,//当前的fiber节点
workInProgress: Fiber,// 新生成的fiber
nextChildren: any,// 新生成的reactElement内容
renderLanes: Lanes,//渲染优先级
) {
if (current === null) {

// 如果没有已经渲染的fiber树,则直接把reactElement内容渲染上去
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
  workInProgress,
  null,
  nextChildren,
  renderLanes,
);

} else {

// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.

// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
  workInProgress,
  current.child,
  nextChildren,
  renderLanes,
);

}
}
复制代码
reconcileChildren的源码并不长,主要做了两件事

如果是首次渲染,则会把已经处理好的fiber树进行挂载。
如果不是首次渲染则调用reconcileChildFibers进行下一步处理。

我们关注一下mountChildFibers和reconcileChildFibers,我们发现这两个函数分别指向ChildReconciler,只是mountChildFibers的参数为false,reconcileChildFibers的参数为true。我们在这里先埋下一个点,看看这个参数对后期的流程有什么影响。

我们继续深入可以发现,ChildReconciler这个函数冰的执行返回了reconcileChildFibers,所以这便是reconcileChildren的核心功能代码所在了。

reconcileChildFibers
function reconcileChildFibers(

returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,

): Fiber | null {

// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.

// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
const isUnkeyedTopLevelFragment =
  typeof newChild === 'object' &&
  newChild !== null &&
  newChild.type === REACT_FRAGMENT_TYPE &&
  newChild.key === null;
if (isUnkeyedTopLevelFragment) {
  newChild = newChild.props.children;
}

// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;

// 处理对象类型
if (isObject) {
  switch (newChild.$$typeof) {
    // REACT_ELEMENT_TYPE类型
    case REACT_ELEMENT_TYPE:
      return placeSingleChild(
        reconcileSingleElement(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        ),
      );
    // REACT_PORTAL_TYPE类型  
    case REACT_PORTAL_TYPE:
      return placeSingleChild(
        reconcileSinglePortal(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        ),
      );
    // REACT_LAZY_TYPE类型    
    case REACT_LAZY_TYPE:
      if (enableLazyElements) {
        const payload = newChild._payload;
        const init = newChild._init;
        // TODO: This function is supposed to be non-recursive.
        return reconcileChildFibers(
          returnFiber,
          currentFirstChild,
          init(payload),
          lanes,
        );
      }
  }
}

// 字符串与数字类型
if (typeof newChild === 'string' || typeof newChild === 'number') {
  return placeSingleChild(
    reconcileSingleTextNode(
      returnFiber,
      currentFirstChild,
      '' + newChild,
      lanes,
    ),
  );
}
// 数组类型 
if (isArray(newChild)) {
  return reconcileChildrenArray(
    returnFiber,
    currentFirstChild,
    newChild,
    lanes,
  );
}

// 可迭代的类型
if (getIteratorFn(newChild)) {
  return reconcileChildrenIterator(
    returnFiber,
    currentFirstChild,
    newChild,
    lanes,
  );
}

if (isObject) {
  throwOnInvalidObjectType(returnFiber, newChild);
}

if (__DEV__) {
  if (typeof newChild === 'function') {
    warnOnFunctionType(returnFiber);
  }
}
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
  // If the new child is undefined, and the return fiber is a composite
  // component, throw an error. If Fiber return types are disabled,
  // we already threw above.
  switch (returnFiber.tag) {
    case ClassComponent: {
      if (__DEV__) {
        const instance = returnFiber.stateNode;
        if (instance.render._isMockFunction) {
          // We allow auto-mocks to proceed as if they're returning null.
          break;
        }
      }
    }
    // Intentionally fall through to the next case, which handles both
    // functions and classes
    // eslint-disable-next-lined no-fallthrough
    case Block:
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      invariant(
        false,
        '%s(...): Nothing was returned from render. This usually means a ' +
          'return statement is missing. Or, to render nothing, ' +
          'return null.',
        getComponentName(returnFiber.type) || 'Component',
      );
    }
  }
}

// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);

}
复制代码
reconcileChildFibers中我们根据入参newChild的类型分别对应着不同的处理:

当修改的内容为REACT_ELEMENT_TYPE类型,调用reconcileSingleElement函数。
当修改的内容为REACT_PORTAL_TYPE类型,调用reconcileSinglePortal函数。
当修改的内容为REACT_LAZY_TYPE类型,递归调用reconcileChildFibers函数。
当修改的内容问纯文本类型,调用reconcileSingleTextNode函数。
当修改的内容为数组类型,调用reconcileChildrenArray函数。
当修改的内容为可迭代类型,调用reconcileChildrenIterator函数

reconcileSingleElement
reconcileSingleElement的源码如下:
function reconcileSingleElement(

returnFiber: Fiber,// 父级
currentFirstChild: Fiber | null, // 父级下diff的第一个
element: ReactElement, // 当前元素
lanes: Lanes, // 优先级

): Fiber {

const key = element.key;
let child = currentFirstChild;
while (child !== null) {
  // TODO: If key === null and child.key === null, then this only applies to
  // the first item in the list.
  if (child.key === key) {
    switch (child.tag) {
      // 如果为Fragment类型,并且key也相等
      case Fragment: {
        if (element.type === REACT_FRAGMENT_TYPE) {

          // get后面的兄弟节点添加Deletion标记,用于dom删除
          deleteRemainingChildren(returnFiber, child.sibling);

          // 通过useFiber复用旧fiber与新的props
          const existing = useFiber(child, element.props.children);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
        break;
      }
      case Block:
        if (enableBlocksAPI) {
          let type = element.type;
          if (type.$$typeof === REACT_LAZY_TYPE) {
            type = resolveLazyType(type);
          }
          if (type.$$typeof === REACT_BLOCK_TYPE) {
            // The new Block might not be initialized yet. We need to initialize
            // it in case initializing it turns out it would match.
            if (
              ((type: any): BlockComponent<any, any>)._render ===
              (child.type: BlockComponent<any, any>)._render
            ) {
              deleteRemainingChildren(returnFiber, child.sibling);
              const existing = useFiber(child, element.props);
              existing.type = type;
              existing.return = returnFiber;
              if (__DEV__) {
                existing._debugSource = element._source;
                existing._debugOwner = element._owner;
              }
              return existing;
            }
          }
        }
      // We intentionally fallthrough here if enableBlocksAPI is not on.
      // eslint-disable-next-lined no-fallthrough
      default: {
        if (
          // 新的ReactElement与旧的current fiber 的key 与 type都相同
          child.elementType === element.type ||
          // Keep this check inline so it only runs on the false path:
          (__DEV__
            ? isCompatibleFamilyForHotReloading(child, element)
            : false)
        ) {
           // 添加标记
          deleteRemainingChildren(returnFiber, child.sibling);
          const existing = useFiber(child, element.props);
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber; 
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
        break;
      }
    }
    // 匹配不上,key相等,type不相等,移除旧的fiber以及后面的兄弟
    deleteRemainingChildren(returnFiber, child);
    break;
  } 


  else 
  
  
  
  {
    // 如果key不同,则标记Deletion,
    deleteChild(returnFiber, child);
  }
  // 遍历其兄弟
  child = child.sibling;
}

if (element.type === REACT_FRAGMENT_TYPE) {
  // 如果是fragment类型,创建fragment,并返回。
  const created = createFiberFromFragment(
    element.props.children,
    returnFiber.mode,
    lanes,
    element.key,
  );
  created.return = returnFiber;
  return created;
} else {
  //如果不是fragment,创建element并返回fiber
  const created = createFiberFromElement(element, returnFiber.mode, lanes);
  created.ref = coerceRef(returnFiber, currentFirstChild, element);
  created.return = returnFiber;
  return created;
}

}
复制代码
根据源码,reconcileSingleElement函数中会遍历当前父级fiber下面的所有子fiber,根据旧的fiber与新生成的ReactElement的key和type进行比较:

如果旧的fiber子节点与新的子节点的key和type不一致,给当前的旧的fiber子节点添加上Deletion标记,继续遍历其兄弟节点。
如果旧的fiber子节点与新的子节点的key是一致的,就会根据当前的节点类型去做匹配处理,通过deleteRemainingChildren给当前子节点以及后面的所有的兄弟节点添加上Deletion标记,并且通过useFiber复用该子节点和该子节点新的props。
如果旧的fiber子节点与新的子节点的类型匹配不上,则会直接给旧的fiber子节点打上Deletion标记,移除子节点以及后面的所有兄弟节点。
如果旧的fiber树遍历完毕,但是发现还没有匹配完的节点,那么会通过createFiberFromFragment,createFiberFromElement创建新的fiber节点,并指向父级fiber。

reconcileSingPortal
function reconcileSinglePortal(

returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
lanes: Lanes,

): Fiber {

const key = portal.key;
let child = currentFirstChild;
while (child !== null) {
  // TODO: If key === null and child.key === null, then this only applies to
  // the first item in the list.
  if (child.key === key) {
    if (
      child.tag === HostPortal &&
      child.stateNode.containerInfo === portal.containerInfo &&
      child.stateNode.implementation === portal.implementation
    ) {
      deleteRemainingChildren(returnFiber, child.sibling);
      const existing = useFiber(child, portal.children || []);
      existing.return = returnFiber;
      return existing;
    } else {
      deleteRemainingChildren(returnFiber, child);
      break;
    }
  } else {
    deleteChild(returnFiber, child);
  }
  child = child.sibling;
}

const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;

}
复制代码
有了上面REACT_ELEMENT_TYPE的讲解,对于REACT_PORTAL_TYPE的源码就有一定的思路了,如果还不知道ReactPortal的作用,直通车 >> Protal有何用处
placeSingleChild
上述的不管是REACT_ELEMENT_TYPE、REACT_PORTAL_TYPE、REACT_LAZY_TYPE都是用了placeSingleChild包裹起来的,我们来看一看他做了什么事情。
function placeSingleChild(newFiber: Fiber): Fiber {

// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFiber.alternate === null) {
  newFiber.flags = Placement;
}
return newFiber;

}
复制代码
那么这里我们就发现了这个shouldTrackSideEffects,还记得我们在前面讲的ChildReconciler函数的入参吗?他只是一个布尔。在挂载阶段shouldTrackSideEffects:false,直接是return newFiber。不必要的标记增加性能开销。而在更新阶段,就必须要做这样的操作。Placement为dom更新时的插入标记。

相关文章
|
Linux Perl
Linux命令教程:使用cat命令查看和处理文件
Linux命令教程:使用cat命令查看和处理文件
1522 0
|
人工智能 网络协议 数据安全/隐私保护
【利用AI让知识体系化】简要了解网络七层协议(一)
【利用AI让知识体系化】简要了解网络七层协议
|
数据可视化 数据挖掘 大数据
【数据挖掘】数据规约中维归约、小波变换、主成分分析的讲解及实战(超详细 附源码)
【数据挖掘】数据规约中维归约、小波变换、主成分分析的讲解及实战(超详细 附源码)
893 0
|
8月前
|
人工智能 自然语言处理 文字识别
快手封号多久能恢复正常?
快手账号封禁恢复全流程技术解析
|
存储 JSON 分布式计算
DataWorks产品使用合集之如何在数据服务中处理JSON数据
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
254 11
|
搜索推荐
教育领域如何应用好AIGC技术
【1月更文挑战第5天】教育领域如何应用好AIGC技术
930 1
教育领域如何应用好AIGC技术
|
存储 Prometheus 运维
【云故事探索】NO.8:揭秘餐饮行业龙头 SaaS 厂商神州商龙的全栈可观测实践
天津市神州商龙科技股份有限公司成立于1998年,专为餐饮行业提供数字化解决方案。公司服务10万余家知名餐饮企业,确保用餐体验的稳定性至关重要。在业务容器化和微服务化过程中,神州商龙面临技术架构多样性、高可用要求及成本控制等挑战。通过尝试自建Prometheus和SkyWalking监控方案,最终选择阿里云Prometheus和日志服务SLS,实现了统一可观测平台,提升了监控效率、缩短故障排查时间、增强系统稳定性和优化资源利用率。未来,神州商龙计划引入机器学习和AI技术,提升自动化运维水平,并进一步整合业务系统监控数据。
【云故事探索】NO.8:揭秘餐饮行业龙头 SaaS 厂商神州商龙的全栈可观测实践
|
机器学习/深度学习 算法
【MATLAB】GA_BP神经网络回归预测算法
【MATLAB】GA_BP神经网络回归预测算法
454 3
【MATLAB】GA_BP神经网络回归预测算法
|
弹性计算 小程序 Android开发
你信吗?有人用云电脑玩《黑神话:悟空》Mac党有福了
本教程详细介绍如何利用阿里云无影云电脑轻松畅玩《黑神话·悟空》游戏,无需下载游戏客户端,开机即可体验。首先需下载无影客户端并购买个人铂金款云电脑(14.9元首月)。随后输入WeGame版或Steam版镜像分享码并选择电竞模式进行配置。最后在云电脑内启动WeGame客户端,添加并更新游戏后即可开始游戏。请注意游戏本身需额外购买。游戏结束后记得关闭云电脑以避免额外收费。更多详情参见阿里云官方文档。
887 1
|
机器学习/深度学习 人工智能 安全
【Python专栏】Python的历史及背景介绍
【Python专栏】Python的历史及背景介绍
1844 6

热门文章

最新文章