React16源码: React中的reconcileChildren的源码实现

reconcileChildren

1 )概述

  • 在更新了一个节点之后,拿到它的props.children
  • 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象
  • 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的
    • 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树
    • 再有一个更新进来之后,state 变化可能会导致一些子节点产生一个新的变化
    • 可能就不能复用之前的 fiber 节点了,它里面的很多东西都变得不一样
    • 大部分情况下所有 fiber 节点都是可以可以重复利用的
    • 这个时候我们根据什么进行判断,是这里面的一个非常重要的一个点
  • 在这里就会拿出react当中的非常重要的key,列表根据key来优化

2 )源码

packages/react-reconciler/src/ReactFiberBeginWork.js#L137

js 复制代码
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    // 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,
      renderExpirationTime,
    );
  } 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,
      renderExpirationTime,
    );
  }
}
  • 可见,根据 current 是否为 null 调用两个方法: mountChildFibers, reconcileChildFibers

    • 两个方法的区别,在第二个参数
    • 在前者方法中传递的是 null, 因为 第一次渲染,不存在 current.child
    • 因为第一次渲染的时候,都是父亲节点先渲染,子节点后渲染
    • 只有在后续的渲染过程当中,已经过第一次渲染了我们的父节点
    • 这个时候才有一个子节点存在,所以这是一个区别
    • 在 react-reconciler/src/ReactFiberBeginWork.js 中 的 reconcileChildren 调用这个 reconcileChildFibers
  • 看一下这两个方法,它们来自于 ReactChildFiber.js

    js 复制代码
    export const reconcileChildFibers = ChildReconciler(true);
    export const mountChildFibers = ChildReconciler(false);
  • 可见,上述两个方法的区别是 参数的 true 和 false

    • 这个参数的意思是: 表示是否要跟踪副作用
  • 进入 ChildReconciler,这个方法 1k 多行,这里不去全部摘抄

  • 用到哪里,逐个分析

整体框架

js 复制代码
// 参数 shouldTrackSideEffects 表示是否要跟踪副作用
function ChildReconciler(shouldTrackSideEffects) {
  // ... 跳过很多代码

  // This API will tag the children with the side-effect of the reconciliation
  // itself. They will be added to the side-effect list as we pass through the
  // children and the parent.
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 省略很多代码
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

  return reconcileChildFibers;
}
  • ChildReconciler 方法的最后, return reconcileChildFibers;,先看下这个方法

    js 复制代码
    // This API will tag the children with the side-effect of the reconciliation
    // itself. They will be added to the side-effect list as we pass through the
    // children and the parent.
    function reconcileChildFibers(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      newChild: any,
      expirationTime: ExpirationTime,
    ): 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.
    
      // newChild 是计算出来的 新children
      // 这个节点是一个 top level 的节点,因为 <></> 这种类型会匹配 REACT_FRAGMENT_TYPE
      // 符合以下就是没有key的 topLevel节点
      const isUnkeyedTopLevelFragment =
        typeof newChild === 'object' &&
        newChild !== null &&
        newChild.type === REACT_FRAGMENT_TYPE &&
        newChild.key === null;
      // 如果匹配了,那么 Fragment 是没有任何的操作更新的, 本身并没有什么实际的意义
      // 如果匹配了,则将newChild赋值
      if (isUnkeyedTopLevelFragment) {
        newChild = newChild.props.children;
      }
    
      // Handle object types
      const isObject = typeof newChild === 'object' && newChild !== null;
    
      // 如果是对象,说明有下面两种情况
      // 下面两种方法调用相似,都是基于 placeSingleChild 传递不同参数
      // 一个是 reconcileSingleElement
      // 另一个是 reconcileSinglePortal
      if (isObject) {
        switch (newChild.$$typeof) {
          // 匹配 REACT_ELEMENT,是通过 React.createElement 产生的
          case REACT_ELEMENT_TYPE:
            return placeSingleChild(
              reconcileSingleElement(
                returnFiber,
                currentFirstChild,
                newChild,
                expirationTime,
              ),
            );
          // 匹配 REACT_PORTAL, 是通过 ReactDOM.createPortal
          case REACT_PORTAL_TYPE:
            return placeSingleChild(
              reconcileSinglePortal(
                returnFiber,
                currentFirstChild,
                newChild,
                expirationTime,
              ),
            );
        }
      }
    
      // 匹配到 string 和 number 类就是 text类型的Node
      if (typeof newChild === 'string' || typeof newChild === 'number') {
        return placeSingleChild(
          reconcileSingleTextNode(
            returnFiber,
            currentFirstChild,
            '' + newChild,
            expirationTime,
          ),
        );
      }
    
      // 匹配到数组
      if (isArray(newChild)) {
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          expirationTime,
        );
      }
      
      // 匹配到可迭代的对象
      if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(
          returnFiber,
          currentFirstChild,
          newChild,
          expirationTime,
        );
      }
    
      // 以上都没有匹配,但仍然是对象,则 throw error
      if (isObject) {
        throwOnInvalidObjectType(returnFiber, newChild);
      }
    
      // 忽略 DEV
      if (__DEV__) {
        if (typeof newChild === 'function') {
          warnOnFunctionType();
        }
      }
    
      // 匹配到 undefined 的对象
      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) {
          // 匹配到 ClassComponent 忽略
          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
          // 匹配到 FunctionComponent 提醒
          case FunctionComponent: {
            const Component = returnFiber.type;
            invariant(
              false,
              '%s(...): Nothing was returned from render. This usually means a ' +
                'return statement is missing. Or, to render nothing, ' +
                'return null.',
              Component.displayName || Component.name || 'Component',
            );
          }
        }
      }
    
      // Remaining cases are all treated as empty.
      // 以上都不符合,则 是 null, 删除现有所有 children
      // 新的 props.children 都是 null, 老的 props 节点都应该被删除
      return deleteRemainingChildren(returnFiber, currentFirstChild);
    }
  • 接下来看不同节点的更新 reconcileSingleElement

    js 复制代码
    // 从当前已有的所有子节点中,找到一个可以复用新的子节点的Fiber对象
    // 复用它之后,把剩下兄弟节点全部删掉
    function reconcileSingleElement(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null, // 是当前已有的fiber节点,初次渲染没有,后续渲染后很可能存在
      element: ReactElement,
      expirationTime: ExpirationTime,
    ): Fiber {
      // 获取 key 和 child
      const key = element.key;
      let child = currentFirstChild;
      // child 存在
      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 === Fragment
              ? element.type === REACT_FRAGMENT_TYPE // 是否是 frament
              : child.elementType === element.type // 新老 element type
          ) {
            // 删除 已存在当前节点的兄弟节点
            // 为何要删除兄弟节点,因为我们这次渲染只有一个节点,老的节点有兄弟节点,新的节点只有一个,删除之
            deleteRemainingChildren(returnFiber, child.sibling);
            const existing = useFiber(
              child,
              element.type === REACT_FRAGMENT_TYPE
                ? element.props.children
                : element.props,
              expirationTime,
            );
            // 挂载属性
            existing.ref = coerceRef(returnFiber, child, element);
            existing.return = returnFiber;
            if (__DEV__) {
              existing._debugSource = element._source;
              existing._debugOwner = element._owner;
            }
            // 找到可复用节点,直接return
            return existing;
          } else {
            // 条件不符合,删除节点并退出 
            deleteRemainingChildren(returnFiber, child);
            break;
          }
        } else {
          // key 不同,则删除
          deleteChild(returnFiber, child);
        }
        // 当前兄弟节点 等于 child 再次进入while循环
        child = child.sibling;
      }
      // 创建新的节点, 基于各种不同的类型,调用不同的创建 Fiber的方式
      if (element.type === REACT_FRAGMENT_TYPE) {
        const created = createFiberFromFragment(
          element.props.children, // 注意这个参数
          returnFiber.mode,
          expirationTime,
          element.key,
        );
        created.return = returnFiber;
        return created;
      } else {
        const created = createFiberFromElement(
          element,
          returnFiber.mode,
          expirationTime,
        );
        created.ref = coerceRef(returnFiber, currentFirstChild, element);
        created.return = returnFiber;
        return created;
      }
    }
    • 进入 createFiberFromFragment

      js 复制代码
      export function createFiberFromFragment(
        elements: ReactFragment,
        mode: TypeOfMode,
        expirationTime: ExpirationTime,
        key: null | string,
      ): Fiber {
        const fiber = createFiber(Fragment, elements, key, mode);
        fiber.expirationTime = expirationTime;
        return fiber;
      }
      
      // This is a constructor function, rather than a POJO constructor, still
      // please ensure we do the following:
      // 1) Nobody should add any instance methods on this. Instance methods can be
      //    more difficult to predict when they get optimized and they are almost
      //    never inlined properly in static compilers.
      // 2) Nobody should rely on `instanceof Fiber` for type testing. We should
      //    always know when it is a fiber.
      // 3) We might want to experiment with using numeric keys since they are easier
      //    to optimize in a non-JIT environment.
      // 4) We can easily go from a constructor to a createFiber object literal if that
      //    is faster.
      // 5) It should be easy to port this to a C struct and keep a C implementation
      //    compatible.
      const createFiber = function(
        tag: WorkTag,
        pendingProps: mixed, // 对于 fragment来说,props只有children, 直接把children作为props
        key: null | string,
        mode: TypeOfMode,
      ): Fiber {
        // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
        return new FiberNode(tag, pendingProps, key, mode);
      };
    • 以上的 fragment 可以在 ReactFiberBeginWork.js 中的 updateFragment 方法中找到验证

      js 复制代码
      function updateFragment(
        current: Fiber | null,
        workInProgress: Fiber,
        renderExpirationTime: ExpirationTime,
      ) {
        // 看这里,直接通过 .pendingProps 而非 props.children 获取,参考上述 createFiber 写明原因
        const nextChildren = workInProgress.pendingProps;
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
        return workInProgress.child;
      }
    • 进入 createFiberFromElement

      js 复制代码
      export function createFiberFromElement(
        element: ReactElement,
        mode: TypeOfMode,
        expirationTime: ExpirationTime,
      ): Fiber {
        let owner = null;
        if (__DEV__) {
          owner = element._owner;
        }
        // 获取各类属性
        const type = element.type;
        const key = element.key;
        const pendingProps = element.props;
        // 创建 Fiber
        const fiber = createFiberFromTypeAndProps(
          type,
          key,
          pendingProps,
          owner,
          mode,
          expirationTime,
        );
        if (__DEV__) {
          fiber._debugSource = element._source;
          fiber._debugOwner = element._owner;
        }
        return fiber;
      }
    • 进入 createFiberFromTypeAndProps

      js 复制代码
      // 这个方法比较复杂,要判断不同的 type 给 fiber 对象增加 不同的 tag
      // 主要是去匹配 fiberTag
      export function createFiberFromTypeAndProps(
        type: any, // React$ElementType
        key: null | string,
        pendingProps: any,
        owner: null | Fiber,
        mode: TypeOfMode,
        expirationTime: ExpirationTime,
      ): Fiber {
        let fiber;
        // 组件tag未知时的初始化配置 未指定状态
        let fiberTag = IndeterminateComponent;
        // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
        let resolvedType = type;
        // 根据不同类型来处理 fiberTag
        if (typeof type === 'function') {
          // 判断是否有 constructor 方法
          /*
          function shouldConstruct(Component: Function) {
            const prototype = Component.prototype;
            // 注意,这里 isReactComponent 是一个 {}
            return !!(prototype && prototype.isReactComponent);
          }
          */
          if (shouldConstruct(type)) {
            fiberTag = ClassComponent;
          }
        } else if (typeof type === 'string') {
          fiberTag = HostComponent;
        } else {
          getTag: switch (type) {
            case REACT_FRAGMENT_TYPE:
              return createFiberFromFragment(
                pendingProps.children,
                mode,
                expirationTime,
                key,
              );
            case REACT_CONCURRENT_MODE_TYPE:
              return createFiberFromMode(
                pendingProps,
                mode | ConcurrentMode | StrictMode,
                expirationTime,
                key,
              );
            case REACT_STRICT_MODE_TYPE:
              return createFiberFromMode(
                pendingProps,
                mode | StrictMode,
                expirationTime,
                key,
              );
            case REACT_PROFILER_TYPE:
              return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
            case REACT_SUSPENSE_TYPE:
              return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
            default: {
              if (typeof type === 'object' && type !== null) {
                switch (type.$$typeof) {
                  case REACT_PROVIDER_TYPE:
                    fiberTag = ContextProvider;
                    break getTag;
                  case REACT_CONTEXT_TYPE:
                    // This is a consumer
                    fiberTag = ContextConsumer;
                    break getTag;
                  case REACT_FORWARD_REF_TYPE:
                    fiberTag = ForwardRef;
                    break getTag;
                  case REACT_MEMO_TYPE:
                    fiberTag = MemoComponent;
                    break getTag;
                  case REACT_LAZY_TYPE:
                    fiberTag = LazyComponent;
                    resolvedType = null;
                    break getTag;
                }
              }
              let info = '';
              if (__DEV__) {
                if (
                  type === undefined ||
                  (typeof type === 'object' &&
                    type !== null &&
                    Object.keys(type).length === 0)
                ) {
                  info +=
                    ' You likely forgot to export your component from the file ' +
                    "it's defined in, or you might have mixed up default and " +
                    'named imports.';
                }
                const ownerName = owner ? getComponentName(owner.type) : null;
                if (ownerName) {
                  info += '\n\nCheck the render method of `' + ownerName + '`.';
                }
              }
              invariant(
                false,
                'Element type is invalid: expected a string (for built-in ' +
                  'components) or a class/function (for composite components) ' +
                  'but got: %s.%s',
                type == null ? type : typeof type,
                info,
              );
            }
          }
        }
        // 最终创建 Fiber
        fiber = createFiber(fiberTag, pendingProps, key, mode);
        fiber.elementType = type;
        fiber.type = resolvedType;
        fiber.expirationTime = expirationTime;
      
        return fiber;
      }
    • 以上,reconcileSingleElement 就完了

  • 进入 reconcileSingleTextNode

    js 复制代码
    function reconcileSingleTextNode(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      textContent: string,
      expirationTime: ExpirationTime,
    ): Fiber {
      // There's no need to check for keys on text nodes since we don't have a
      // way to define them.
      // 处理 原生 标签
      if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
        // We already have an existing node so let's just update it and delete
        // the rest.
        // 删除兄弟节点,新节点只是一个纯的文本节点
        deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
        // 基于 useFiber 复用当前节点
        const existing = useFiber(currentFirstChild, textContent, expirationTime);
        existing.return = returnFiber;
        return existing;
      }
      // The existing first child is not a text node so we need to create one
      // and delete the existing ones.
      deleteRemainingChildren(returnFiber, currentFirstChild);
      const created = createFiberFromText(
        textContent,
        returnFiber.mode,
        expirationTime,
      );
      created.return = returnFiber;
      return created;
    }
    • 这个流程就比较简单了
  • 现在我们主要看下 deleteRemainingChildren 这个api

    js 复制代码
    function deleteRemainingChildren(
      returnFiber: Fiber, // 当前正在更新的节点
      currentFirstChild: Fiber | null, // 子节点
    ): null {
      // 这是第一次渲染的时候,没有子节点
      // 直接 return
      if (!shouldTrackSideEffects) {
        // Noop.
        return null;
      }
    
      // TODO: For the shouldClone case, this could be micro-optimized a bit by
      // assuming that after the first child we've already added everything.
      // 一个个的删除
      let childToDelete = currentFirstChild;
      while (childToDelete !== null) {
        deleteChild(returnFiber, childToDelete);
        childToDelete = childToDelete.sibling;
      }
      return null;
    }
    • 进入 deleteChild

      js 复制代码
      // 可以看到这里没有实施删除的操作,只是改变了节点上的 effectTag
      // 这里我们只更新 Fiber Tree, 如果真的要 delete 就会影响到 dom节点
      // 所以删除的流程不在这里做,只有在下个阶段,也就是 commit 阶段来做
      // 就是把Fiber Tree 需要更新的流程都过一遍之后,把整个需要更新的属性
      // 通过 firstEffect,lastEffect 这种链条,最终链到根节点上面
      // 最终要执行更新 只需要在 commit 阶段拿到 根节点上面的 这种链条把每个节点去执行即可
      // 在处理更新的时候任务是可以中断的,但是在 commit 阶段任务是不可中断的
      function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
        if (!shouldTrackSideEffects) {
          // Noop.
          return;
        }
        // Deletions are added in reversed order so we add it to the front.
        // At this point, the return fiber's effect list is empty except for
        // deletions, so we can just append the deletion to the list. The remaining
        // effects aren't added until the complete phase. Once we implement
        // resuming, this may not be true.
        const last = returnFiber.lastEffect;
        // 存在 last
        if (last !== null) {
          last.nextEffect = childToDelete;
          returnFiber.lastEffect = childToDelete;
        } else {
          // 不存在 last
          returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
        }
        childToDelete.nextEffect = null; // 已经是要删除的节点,上面的其他副作用都是没有任何意义的
        // effectTag 就是告诉后续的 commit 阶段,对于这个节点需要执行什么操作,可以看到,这里是删除
        childToDelete.effectTag = Deletion; // 只需要设置这个即可
      }
  • reconcileChildrenArrayreconcileChildrenIterator 这两个api比较复杂,先跳过

  • 其他的 API 都是比较简单了

相关推荐
代码匠心1 小时前
AI 自动编程:一句话设计高颜值博客
前端·ai·ai编程·claude
_AaronWong2 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode2 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441942 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo2 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭3 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木3 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮3 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati3 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉3 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain