Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FunctionComponent 更新过程 #7

Open
lz-lee opened this issue Aug 27, 2019 · 0 comments
Open

FunctionComponent 更新过程 #7

lz-lee opened this issue Aug 27, 2019 · 0 comments

Comments

@lz-lee
Copy link
Owner

lz-lee commented Aug 27, 2019

函数式组件(FunctionComponent)更新过程

case FunctionComponent: {
  const Component = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
    workInProgress.elementType === Component
      ? unresolvedProps
      : resolveDefaultProps(Component, unresolvedProps);
  return updateFunctionComponent(
    // workInProgress.alternate 当前fiber
    current,
    workInProgress,
    // 组件类型
    Component,
    resolvedProps,
    // 最高更新优先级
    renderExpirationTime,
  );
}

updateFunctionComponent

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  if (__DEV__) {
    // ...
  } else {
    // Component 即组件的那个方法
    nextChildren = Component(nextProps, context);
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  // 把 nextChildren 这些 ReactElement 变成 Fiber 对象, 挂载 在 workInProgress.child 上
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

reconcileChildren

  • 根据 fiber.children 生成 fiber 子树
  • 判断 Fiber 对象是否可以复用,在第一次渲染就渲染了 fiber 子树,state 变化可能会导致某些子节点产生变化而不能复用,但是大部分是可以复用的
  • 列表根据 key 优化
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,
    );
  }
}

// 更新渲染
export const reconcileChildFibers = ChildReconciler(true);
// 首次渲染
export const mountChildFibers = ChildReconciler(false);

reconcileChildFibers

  • reconcileChildFibersChildReconciler 最终返回的函数
  • 先判断 newChild 是不是 Fragment 节点,如果是 Fragment 则将 newChild 赋值为 newChild.props.children
  • 接着判断 newChild 是不是 isObject
    • REACT_ELEMENT_TYPEreconcileSingleElement
    • REACT_PORTAL_TYPEreconcileSinglePortal
  • 判断 string or numberreconcileSingleTextNode
  • 判断 isArrayreconcileChildrenArray
  • 判断是 iterator 函数: reconcileChildrenIterator
  • 都不符合以上情况,并且还是 isObject,则抛出错误
  • newChildundefined 并且非 Fragment,提醒没有返回值
  • null 的情况,新的 propschildrennull,则把现有的所有 children 都删掉
function reconcileChildFibers(
    // workInProgress
    returnFiber: Fiber,
    // 原有子节点 current.child
    currentFirstChild: Fiber | null,
    // 组件执行后返回的 nextChildren, nextChildren = Component(nextProps, context);
    newChild: any,
    // 最高优先级过期时间
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 判断是不是 Fragment
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
      // 如果是 Fragment,只渲染 props.children 就行了
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' && newChild !== null;
    // 对象类型
    if (isObject) {
      switch (newChild.$$typeof) {
        // React.element
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
        // React.portal
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
      }
    }
    // 文本类型
    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,
      );
    }
    // 还是对象那么就抛出错误
    if (isObject) {
      throwOnInvalidObjectType(returnFiber, newChild);
    }
    if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
      // 没有返回值的情况,给提示
    }
    // 为 null 的情况,删除所有子节点
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

React Element 类型——reconcileSingleElement

  • 找可以复用的节点:从当前已有的所有子节点当中找到一个可以复用新的子节点的那个fiber对象,复用它然后直接返回,把剩下的兄弟节点删掉
  • 通过判断 elementType 来创建新的节点
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  expirationTime: ExpirationTime,
): Fiber {
  // 新 child 的key
  const key = element.key;
  // 已有的fiber节点,更新渲染才不为 null
  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.
    // 判断新老的key是否相同
    if (child.key === key) {
      if (
        // 原 child 是 Fragment 且新 element type 也是 Fragment
        child.tag === Fragment
          ? element.type === REACT_FRAGMENT_TYPE
          // 或者判断 elementType 是否相同
          : child.elementType === element.type
      ) {
        // 复用当前节点,删除不用的兄弟节点(这里删除都是标记删除,而非真正删除)
        deleteRemainingChildren(returnFiber, child.sibling);
        // 复用这个老的child
        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 existing;
      } else {
        // key 相同,但类型不相同,删除老的child,退出循环
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      // 不相同则全部删除
      deleteChild(returnFiber, child);
    }
    // 兄弟节点继续寻找可复用的
    child = child.sibling;
  }
  // 创建新的节点
  if (element.type === REACT_FRAGMENT_TYPE) {
    // Fragment 传的elements传入的是 element.props.children, --> createFiber接收的 penddingProps 即这个 children
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      expirationTime,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    // 根据 element type来创建不同 fiber 对象
    const created = createFiberFromElement(
      element,
      returnFiber.mode,
      expirationTime,
    );
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

useFiber 复用节点

function useFiber(
    fiber: Fiber,
    pendingProps: mixed,
    expirationTime: ExpirationTime,
  ): Fiber {
    // 复用的时候 fiber.alternate 已不为 null,就不需要再 creatFiber 了
    const clone = createWorkInProgress(fiber, pendingProps, expirationTime);
    clone.index = 0;
    clone.sibling = null;
    return clone;
  }

deleteChild 标记删除 和 deleteRemainingChildren

function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
  if (!shouldTrackSideEffects) {
    // Noop.
    return;
  }
  const last = returnFiber.lastEffect;
  if (last !== null) {
    last.nextEffect = childToDelete;
    returnFiber.lastEffect = childToDelete;
  } else {
    returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
  }
  // 清空副作用
  childToDelete.nextEffect = null;
  // 标记删除,更新 fiberTree,直接删除会影响到 dom 节点,真正删除在 commit 阶段,这里的任务是可以中断的,而commit 是不可中断的
  childToDelete.effectTag = Deletion;
}

function deleteRemainingChildren(
  returnFiber: Fiber, // 当前更新的节点
  currentFirstChild: Fiber | null, // 子节点
): null {
  // 首次渲染 直接返回
  if (!shouldTrackSideEffects) {
    // Noop.
    return null;
  }

  let childToDelete = currentFirstChild;
  // 循环删除要删除的子节点的兄弟节点
  while (childToDelete !== null) {
    deleteChild(returnFiber, childToDelete);
    childToDelete = childToDelete.sibling;
  }
  return null;
}

数组类型——reconcileChildrenArray

  • 尽量减少数组的遍历次数来达到判断节点是否可复用的过程

  • 第一次遍历

    • 主要目的是找到不能复用的那个节点为止则跳出循环,newIdx 则为能够有几个能够复用的节点的index
    • updateSlot 中通过判断新老 Key 是否相同来复用
      • updateSlot 返回 null 表示不能复用, 直接 break
      • 文本节点:oldFiberkey,而 newChildtextNode 直接返回 null , 因为文本节点是没有 Key
      • 对于 textNode、Fragment、Element 都会判断 oldFiber 不为 null 就复用,为 null 则创建新的
    • 复用则构建链结构
  • 第一次遍历完 或者 break

    • newIdx 等于 newChildren.length 说明已经在 updateSlot 中创建新的对象了, 新数组操作完成了, 所有新节点都已经创建
      • 直接标记删除 oldFiber
    • oldFibernull,老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的
  • 最后 newChildren 还没创建完,oldFiber 还不为 null 的情况,数组存在顺序的变化

    • 这是一次完整的遍历
    • updateFromMap: 根据 keyindex 创建 map 对象,通过 map 对象在 oldFiber 里找到可以复用的或者创建新的
function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  expirationTime: ExpirationTime,
): Fiber | null {
  // 要返回的结果
  let resultingFirstChild: Fiber | null = null;
  // 用于构建链结构的中间变量
  let previousNewFiber: Fiber | null = null;

  // 上一次渲染完成后第一个 child 节点, current.child
  let oldFiber = currentFirstChild;
  let lastPlacedIndex = 0;
  let newIdx = 0;
  // 下一个老的节点
  let nextOldFiber = null;
  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
    // 老的 fiber 的 index 位置与新的不同,位置不匹配
    if (oldFiber.index > newIdx) {
      // 将 oldFiber 赋值给 nextOldFiber 暂存
      nextOldFiber = oldFiber;
      oldFiber = null;
    } else {
      // 没变化则下一个老的节点为当前的节点的兄弟节点
      nextOldFiber = oldFiber.sibling;
    }
    // 
    const newFiber = updateSlot(
      returnFiber,
      oldFiber,
      newChildren[newIdx],
      expirationTime,
    );
    // key 相同根据类型确定复用类型
    // 文本节点没有key,为 null 表示不能复用, 能复用也有复用复用节点和新建节点的区分 
    if (newFiber === null) {
      // TODO: This breaks on empty slots like null children. That's
      // unfortunate because it triggers the slow path all the time. We need
      // a better way to communicate whether this was a miss or null,
      // boolean, undefined, etc.
      // 重置 oldFiber,跳出循环,即找到不能复用的那个节点为止的fiber,那么 newIdx 则表示有多少个节点相同的那个index
      if (oldFiber === null) {
        oldFiber = nextOldFiber;
      }
      break;
    }
    // 更新渲染的情况
    if (shouldTrackSideEffects) {
      // 没有复用,说明这是这是创建新的节点, 则要标记删除老的
      if (oldFiber && newFiber.alternate === null) {
        // We matched the slot, but we didn't reuse the existing fiber, so we
        // need to delete the existing child.
        deleteChild(returnFiber, oldFiber);
      }
    }
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    // previousNewFiber 中间变量,将数组构建成链表结构
    if (previousNewFiber === null) {
      // TODO: Move out of the loop. This only happens for the first run.
      // 首个子节点
      resultingFirstChild = newFiber;
    } else {
      // TODO: Defer siblings if we're not at the right index for this slot.
      // I.e. if we had null values before, then we want to defer this
      // for each null value. However, we also don't want to call updateSlot
      // with the previous one.
      // 构建链表结构
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
    oldFiber = nextOldFiber;
  }
  // 新数组操作完,所有新的 children 都全部创建 fiber 节点了
  if (newIdx === newChildren.length) {
    // We've reached the end of the new children. We can delete the rest.
    // 删除老的
    deleteRemainingChildren(returnFiber, oldFiber);
    // 返回首个子节点,其他节点是通过 sibling 指向下去
    return resultingFirstChild;
  }

  if (oldFiber === null) {
    // If we don't have any more existing children we can choose a fast path
    // since the rest will all be insertions.
    // 老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(
        returnFiber,
        newChildren[newIdx],
        expirationTime,
      );
      if (!newFiber) {
        continue;
      }
      // 同样 placeChild 标记移动位置
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        // TODO: Move out of the loop. This only happens for the first run.
        resultingFirstChild = newFiber;
      } else {
        // 构建链表结构
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }

  // Add all children to a key map for quick lookups.
  // newChildren 还没创建完,oldFiber 还不为null
  // 数组存在顺序的变化,在 oldFiber 里找到可以复用的,通过 map 快速寻找
  // 根据 key 或 index 创建 map 对象
  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

  // 一次完整的遍历
  // Keep scanning and use the map to restore deleted items as moves.
  for (; newIdx < newChildren.length; newIdx++) {
    // 根据 map 找可以复用的节点 或 创建新节点,跟 updateSlot 相似
    const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren[newIdx],
      expirationTime,
    );
    if (newFiber) {
      if (shouldTrackSideEffects) {
        // 复用的情况
        if (newFiber.alternate !== null) {
          // The new fiber is a work in progress, but if there exists a
          // current, that means that we reused the fiber. We need to delete
          // it from the child list so that we don't add it to the deletion
          // list.
          // 从 map 里删除
          existingChildren.delete(
            newFiber.key === null ? newIdx : newFiber.key,
          );
        }
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
  }

  if (shouldTrackSideEffects) {
    // Any existing children that weren't consumed above were deleted. We need
    // to add them to the deletion list.
    // 最后剩下的是没有被复用的,全部删除
    existingChildren.forEach(child => deleteChild(returnFiber, child));
  }

  return resultingFirstChild;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant