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,进行了详细介绍,其余对象及编码将再下一篇文章中进行详细说明。
13876 0
|
资源调度 前端开发 JavaScript
Tailwind CSS那些事儿
Tailwind CSS那些事儿
824 1
|
Web App开发 移动开发 前端开发
b 站, 掘金都在用的 webp 是什么?怎么用?
如果你现在用 PC 端浏览器看这篇文章,不妨打开控制台,cltr + c 一下去看下掘金图片的后缀/格式究竟是什么?
|
程序员 C++ 开发者
《Lua游戏开发实践指南》一1.3为什么使用Lua
本节书摘来华章计算机《Lua游戏开发实践指南》一书中的第1章 ,第1.3节,(美)Paul SchuytemaMark Manyen著 田剑译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2507 0
|
1天前
|
人工智能 自然语言处理 Shell
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
仅用3分钟,百炼调用满血版Deepseek-r1 API,享受百万免费Token。阿里云提供零门槛、快速部署的解决方案,支持云控制台和Cloud Shell两种方式,操作简便。Deepseek-r1满血版在推理能力上表现出色,尤其擅长数学、代码和自然语言处理任务,使用过程中无卡顿,体验丝滑。结合Chatbox工具,用户可轻松掌控模型,提升工作效率。阿里云大模型服务平台百炼不仅速度快,还确保数据安全,值得信赖。
70745 18
深度评测 | 仅用3分钟,百炼调用满血版 Deepseek-r1 API,百万Token免费用,简直不要太爽。
|
3天前
|
人工智能 API 网络安全
用DeepSeek,就在阿里云!四种方式助您快速使用 DeepSeek-R1 满血版!更有内部实战指导!
DeepSeek自发布以来,凭借卓越的技术性能和开源策略迅速吸引了全球关注。DeepSeek-R1作为系列中的佼佼者,在多个基准测试中超越现有顶尖模型,展现了强大的推理能力。然而,由于其爆火及受到黑客攻击,官网使用受限,影响用户体验。为解决这一问题,阿里云提供了多种解决方案。
15168 32
|
11天前
|
机器学习/深度学习 人工智能 自然语言处理
PAI Model Gallery 支持云上一键部署 DeepSeek-V3、DeepSeek-R1 系列模型
DeepSeek 系列模型以其卓越性能在全球范围内备受瞩目,多次评测中表现优异,性能接近甚至超越国际顶尖闭源模型(如OpenAI的GPT-4、Claude-3.5-Sonnet等)。企业用户和开发者可使用 PAI 平台一键部署 DeepSeek 系列模型,实现 DeepSeek 系列模型与现有业务的高效融合。
|
11天前
|
人工智能 搜索推荐 Docker
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
DeepSeek R1 + LobeChat + Ollama:快速本地部署模型,创建个性化 AI 助手
3182 116
手把手教你使用 Ollama 和 LobeChat 快速本地部署 DeepSeek R1 模型,创建个性化 AI 助手
|
3天前
|
并行计算 PyTorch 算法框架/工具
本地部署DeepSeek模型
要在本地部署DeepSeek模型,需准备Linux(推荐Ubuntu 20.04+)或兼容的Windows/macOS环境,配备NVIDIA GPU(建议RTX 3060+)。安装Python 3.8+、PyTorch/TensorFlow等依赖,并通过官方渠道下载模型文件。配置模型后,编写推理脚本进行测试,可选使用FastAPI服务化部署或Docker容器化。注意资源监控和许可协议。
901 5
|
6天前
|
人工智能 自然语言处理 API
DeepSeek全尺寸模型上线阿里云百炼!
阿里云百炼平台近日上线了DeepSeek-V3、DeepSeek-R1及其蒸馏版本等六款全尺寸AI模型,参数量达671B,提供高达100万免费tokens。这些模型在数学、代码、自然语言推理等任务上表现出色,支持灵活调用和经济高效的解决方案,助力开发者和企业加速创新与数字化转型。示例代码展示了如何通过API使用DeepSeek-R1模型进行推理,用户可轻松获取思考过程和最终答案。