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更新时的插入标记。

相关文章
|
存储 NoSQL API
redis的5种对象与8种数据结构(一)
【说明】  本文是对redis对象、数据结构的整理说明,因为内容较多,本篇文章只对对象结构,1种对象——字符串对象,以及字符串对象所对应的两种编码——raw和embstr,进行了详细介绍,其余对象及编码将再下一篇文章中进行详细说明。
13838 0
|
7月前
|
前端开发 JavaScript 编译器
React 团队最近在忙啥?
React 团队最近在忙啥?
|
资源调度 前端开发 JavaScript
Tailwind CSS那些事儿
Tailwind CSS那些事儿
803 1
|
Web App开发 移动开发 前端开发
b 站, 掘金都在用的 webp 是什么?怎么用?
如果你现在用 PC 端浏览器看这篇文章,不妨打开控制台,cltr + c 一下去看下掘金图片的后缀/格式究竟是什么?
|
程序员 C++ 开发者
《Lua游戏开发实践指南》一1.3为什么使用Lua
本节书摘来华章计算机《Lua游戏开发实践指南》一书中的第1章 ,第1.3节,(美)Paul SchuytemaMark Manyen著 田剑译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2474 0
|
6天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
23天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
27天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
18天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
11705 12
|
12天前
|
人工智能 自然语言处理 前端开发
100个降噪蓝牙耳机免费领,用通义灵码从 0 开始打造一个完整APP
打开手机,录制下你完成的代码效果,发布到你的社交媒体,前 100 个@玺哥超Carry、@通义灵码的粉丝,可以免费获得一个降噪蓝牙耳机。
5335 14