lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

FunctionComponent 更新过程 #7

Open lz-lee opened 5 years ago

lz-lee commented 5 years ago

函数式组件(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

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

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

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