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,进行了详细介绍,其余对象及编码将再下一篇文章中进行详细说明。
13862 0
|
资源调度 前端开发 JavaScript
Tailwind CSS那些事儿
Tailwind CSS那些事儿
819 1
|
Web App开发 移动开发 前端开发
b 站, 掘金都在用的 webp 是什么?怎么用?
如果你现在用 PC 端浏览器看这篇文章,不妨打开控制台,cltr + c 一下去看下掘金图片的后缀/格式究竟是什么?
|
程序员 C++ 开发者
《Lua游戏开发实践指南》一1.3为什么使用Lua
本节书摘来华章计算机《Lua游戏开发实践指南》一书中的第1章 ,第1.3节,(美)Paul SchuytemaMark Manyen著 田剑译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2498 0
|
15天前
|
供应链 监控 安全
对话|企业如何构建更完善的容器供应链安全防护体系
阿里云与企业共筑容器供应链安全
171335 12
|
18天前
|
供应链 监控 安全
对话|企业如何构建更完善的容器供应链安全防护体系
随着云计算和DevOps的兴起,容器技术和自动化在软件开发中扮演着愈发重要的角色,但也带来了新的安全挑战。阿里云针对这些挑战,组织了一场关于云上安全的深度访谈,邀请了内部专家穆寰、匡大虎和黄竹刚,深入探讨了容器安全与软件供应链安全的关系,分析了当前的安全隐患及应对策略,并介绍了阿里云提供的安全解决方案,包括容器镜像服务ACR、容器服务ACK、网格服务ASM等,旨在帮助企业构建涵盖整个软件开发生命周期的安全防护体系。通过加强基础设施安全性、技术创新以及倡导协同安全理念,阿里云致力于与客户共同建设更加安全可靠的软件供应链环境。
150296 32
|
26天前
|
弹性计算 人工智能 安全
对话 | ECS如何构筑企业上云的第一道安全防线
随着中小企业加速上云,数据泄露、网络攻击等安全威胁日益严重。阿里云推出深度访谈栏目,汇聚产品技术专家,探讨云上安全问题及应对策略。首期节目聚焦ECS安全性,提出三道防线:数据安全、网络安全和身份认证与权限管理,确保用户在云端的数据主权和业务稳定。此外,阿里云还推出了“ECS 99套餐”,以高性价比提供全面的安全保障,帮助中小企业安全上云。
201962 14
对话 | ECS如何构筑企业上云的第一道安全防线
|
4天前
|
机器学习/深度学习 自然语言处理 PyTorch
深入剖析Transformer架构中的多头注意力机制
多头注意力机制(Multi-Head Attention)是Transformer模型中的核心组件,通过并行运行多个独立的注意力机制,捕捉输入序列中不同子空间的语义关联。每个“头”独立处理Query、Key和Value矩阵,经过缩放点积注意力运算后,所有头的输出被拼接并通过线性层融合,最终生成更全面的表示。多头注意力不仅增强了模型对复杂依赖关系的理解,还在自然语言处理任务如机器翻译和阅读理解中表现出色。通过多头自注意力机制,模型在同一序列内部进行多角度的注意力计算,进一步提升了表达能力和泛化性能。
|
8天前
|
存储 人工智能 安全
对话|无影如何助力企业构建办公安全防护体系
阿里云无影助力企业构建办公安全防护体系
1253 10
|
10天前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。