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

portalComponent、ForwardRef、Mode、memo 更新 #10

Open
lz-lee opened this issue Sep 5, 2019 · 0 comments
Open

portalComponent、ForwardRef、Mode、memo 更新 #10

lz-lee opened this issue Sep 5, 2019 · 0 comments

Comments

@lz-lee
Copy link
Owner

lz-lee commented Sep 5, 2019

portalComponent

  • createPortal 创建 portal,返回一个类似 reactElement 的对象
function createPortal(
  children: ReactNodeList,
  container: DOMContainer,
  key: ?string = null,
) {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  // TODO: pass ReactDOM portal implementation as third argument
  return ReactPortal.createPortal(children, container, null, key);
}

function createPortal(
  children: ReactNodeList,
  containerInfo: any,
  // TODO: figure out the API for cross-renderer implementation.
  implementation: any,
  key: ?string = null,
): ReactPortal {
  return {
    // This tag allow us to uniquely identify this as a React Portal
    $$typeof: REACT_PORTAL_TYPE,
    key: key == null ? null : '' + key,
    children,
    containerInfo,
    implementation,
  };
}
  • 调和子节点中为 portal 类型组件创建 fiber 节点
// reconcileChildFibers
function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  expirationTime: ExpirationTime,
): Fiber | null {
  // ....
  const isObject = typeof newChild === 'object' && newChild !== null;
  if (isObject) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            expirationTime,
          ),
        );
      case REACT_PORTAL_TYPE:
        return placeSingleChild(
          reconcileSinglePortal(
            returnFiber,
            currentFirstChild,
            newChild,
            expirationTime,
          ),
        );
    }
  }
  // ....
}

// reconcileSinglePortal
function reconcileSinglePortal(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  portal: ReactPortal,
  expirationTime: ExpirationTime,
): 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 &&
        // 原有子节点的 container 与新的 portal<createPortal返回的那个对象> 相同,说明可复用
        child.stateNode.containerInfo === portal.containerInfo &&
        child.stateNode.implementation === portal.implementation
      ) {
        // 删除兄弟节点
        deleteRemainingChildren(returnFiber, child.sibling);
        // 复用当前节点
        const existing = useFiber(
          child,
          portal.children || [],
          expirationTime,
        );
        existing.return = returnFiber;
        return existing;
      } else {
        // 不能复用删除所有
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      // key 不相同 删除所有
      deleteChild(returnFiber, child);
    }
    // 循环找知道找到可复用的节点为止
    child = child.sibling;
  }

  // 如果没有可以复用的,那么要创建新的 fiber

  const created = createFiberFromPortal(
    portal,
    returnFiber.mode,
    expirationTime,
  );
  created.return = returnFiber;
  return created;
}

createFiberFromPortal 创建 portal 类型的 fiber

function createFiberFromPortal(
  portal: ReactPortal,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
): Fiber {
  // 将 portal.children 直接赋值为 pendingProps
  const pendingProps = portal.children !== null ? portal.children : [];
  // 创建 fiber, tag 为 HostPortal
  const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
  fiber.expirationTime = expirationTime;

  // 因为 portal 有单独的挂载点,需要手动添加 stateNode
  // 参考 RootFiber.stateNode === FiberRoot
  fiber.stateNode = {
    containerInfo: portal.containerInfo,
    pendingChildren: null, // Used by persistent updates
    implementation: portal.implementation,
  };
  return fiber;
}

updatePortalComponent 更新 fiber 节点

case HostPortal:
  return updatePortalComponent(
    current,
    workInProgress,
    renderExpirationTime,
  );

function updatePortalComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  // 有单独的挂载点,container 需要 push
  pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
  // 获取新的 children
  const nextChildren = workInProgress.pendingProps;
  // 首次调用
  if (current === null) {
    // Portals are special because we don't append the children during mount
    // but at commit. Therefore we need to track insertions which the normal
    // flow doesn't do during mount. This doesn't happen at the root because
    // the root always starts with a "current" with a null child.
    // TODO: Consider unifying this with how the root works.
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    // 相当于 mountChildFibers
    // 更新渲染
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
  }
  return workInProgress.child;
}

ForwardRef

  • forwardRef 用来传递 ref
  • 调和子节点创建 fiber 是在 reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps 中将 fiberTag 标记为 ForwardRef
function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  if (__DEV__) {
    if (typeof render !== 'function') {
      warningWithoutStack(
        false,
        'forwardRef requires a render function but was given %s.',
        render === null ? 'null' : typeof render,
      );
    } else {
      warningWithoutStack(
        // Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
        render.length === 0 || render.length === 2,
        'forwardRef render functions accept exactly two parameters: props and ref. %s',
        render.length === 1
          ? 'Did you forget to use the ref parameter?'
          : 'Any additional parameter will be undefined.',
      );
    }

    if (render != null) {
      warningWithoutStack(
        render.defaultProps == null && render.propTypes == null,
        'forwardRef render functions do not support propTypes or defaultProps. ' +
          'Did you accidentally pass a React component?',
      );
    }
  }

  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render, // render 即 function component
  };
}

updateForwardRef 更新

case ForwardRef: {
  const type = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
    workInProgress.elementType === type
      ? unresolvedProps
      : resolveDefaultProps(type, unresolvedProps);
  return updateForwardRef(
    current,
    workInProgress,
    type,
    resolvedProps,
    renderExpirationTime,
  );
}

// updateForwardRef
function updateForwardRef(
  current: Fiber | null,
  workInProgress: Fiber,
  type: any,
  nextProps: any,
  renderExpirationTime: ExpirationTime,
) {
  // render 即 React.forwardRef 传入的 function component
  const render = type.render;
  // 调用组件产生的 ref
  const ref = workInProgress.ref;
  if (hasLegacyContextChanged()) {
    // Normally we can bail out on props equality but if context has changed
    // we don't do the bailout and we have to reuse existing props instead.
  } else if (workInProgress.memoizedProps === nextProps) {
    // 之前的 ref 
    const currentRef = current !== null ? current.ref : null;
    // 两个ref 相同则跳过更新
    if (ref === currentRef) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }

  let nextChildren;
  if (__DEV__) {
    ReactCurrentOwner.current = workInProgress;
    ReactCurrentFiber.setCurrentPhase('render');
    nextChildren = render(nextProps, ref);
    ReactCurrentFiber.setCurrentPhase(null);
  } else {
    // React.forwardRef((props, ref) => {})
    // 传递 ref 并获取新 children,没有传 context
    nextChildren = render(nextProps, ref);
  }
  // 调和子节点
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

Mode 组件

  • React 提供的原生组件,包括 <ConCurrentmode /><StrictMode />
  • 作用:在 Fiber 对象上记录所有子节点所对应的渲染模式,在创建更新时候根据 mode 创建 expirationTime

typeofMode

export type TypeOfMode = number;

export const NoContext = 0b000;
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
  • 更新调和子节点在 createFiberFromElement -> createFiberFromTypeAndProps 中为 mode 组件创建 fiber 节点,这里的传入的 modereturnFiber(即父组件) 上的 mode,而父组件上的 mode 最终来自 hostRootFiber 上,默认为 NoContext
  • 对应的 Mode组件 它会有一个特殊的的 Fiber 对象上的 mode 属性
    • conCurrentMode 传入的 modeNoContext | ConcurrentMode | StrictMode -> 0b011
    • StrictMode 传入的 ModeNoContext | StrictMode -> 0b010
  • 而在调和 Mode 组件 的子节点过程中,传入的 mode 则是这个 Mode 组件fiber 对象上的 mode 属性,对于 Mode 组件 它所有子树上的子节点都会具有这个 Mode 组件fiber 对象上的 mode 属性,以此来记录这个子树处于那个渲染模式下,才会有后期创建更新时根据 mode 属性如 ConcurrentMode 会对 expirationTime 有个特殊的计算过程
// 
function createHostRootFiber(isConcurrent: boolean): Fiber {
  // 这里在调用 ReactDOM.render 方法时 isConcurrent 为false,所以传递到子节点一直都是 NoContext
  let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;

  if (enableProfilerTimer && isDevToolsPresent) {
    // Always collect profile timings when DevTools are present.
    // This enables DevTools to start capturing timing at any point–
    // Without some nodes in the tree having empty base times.
    mode |= ProfileMode;
  }

  return createFiber(HostRoot, null, null, mode);
}

// reconcileChildFibers
function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
  // ...
  case REACT_ELEMENT_TYPE:
    return placeSingleChild(
      reconcileSingleElement(
        returnFiber,
        currentFirstChild,
        newChild,
        expirationTime,
      ),
    );
  // ...  
}

// reconcileSingleElement

function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    expirationTime: ExpirationTime,
  ): Fiber {
    // ....

    // 创建新的节点
    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;
    }
}

// createFiberFromTypeAndProps
function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
) {
  let fiber;

  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;
  if (typeof type === 'function') {
    if (shouldConstruct(type)) {
      // component 组件
      fiberTag = ClassComponent;
    }
  } else if (typeof type === 'string') {
    fiberTag = HostComponent;
  } else {
    getTag: switch (type) {
      case REACT_FRAGMENT_TYPE:
        return createFiberFromFragment(
          pendingProps.children,
          mode,
          expirationTime,
          key,
        );
      // concurrent mode
      // NoContext | ConcurrentMode | StrictMode -> 0b011
      case REACT_CONCURRENT_MODE_TYPE:
        return createFiberFromMode(
          pendingProps,
          mode | ConcurrentMode | StrictMode,
          expirationTime,
          key,
        );
      // strict mode
      // NoContext | StrictMode -> 0b010
      case REACT_STRICT_MODE_TYPE:
        return createFiberFromMode(
          pendingProps,
          mode | StrictMode,
          expirationTime,
          key,
        );
  // ....
  return fiber;
}

createFiberFromMode 根据 mode 创建 Fiber 节点

  • type 赋值:mode & ConcurrentMode 等于 NoContext 则为 StrictMode,否则为 ConCurrentMode
  • 依次给子节点也添加上父组件 mode 对应的 type
function createFiberFromMode(
  pendingProps: any,
  mode: TypeOfMode,
  expirationTime: ExpirationTime,
  key: null | string,
): Fiber {
  const fiber = createFiber(Mode, pendingProps, key, mode);

  // TODO: The Mode fiber shouldn't have a type. It has a tag.
  // 0b011 & 0b001 => 0b001 => ConCurrentMode
  // 0b010 & 0b001 => 0b000 => StrictMode
  const type =
    (mode & ConcurrentMode) === NoContext
      ? REACT_STRICT_MODE_TYPE
      : REACT_CONCURRENT_MODE_TYPE;
  fiber.elementType = type;
  fiber.type = type;

  fiber.expirationTime = expirationTime;
  return fiber;
}

updateMode更新 Mode

function updateMode(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
) {
  const nextChildren = workInProgress.pendingProps.children;
  // children 来源?
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

Memo 组件

  • React.memo(com, compare) 创建一个具有 pureComponent 特性的 functionComponent

React.memo()

  • 调和子节点过程中,根据 $$typeof 标记 fiberTagMemoComponent
function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  return {
    $$typeof: REACT_MEMO_TYPE,
    // 这个 type 即传入进来的 function Component
    type,
    compare: compare === undefined ? null : compare,
  };
}

// function createFiberFromTypeAndProps 
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;
    }
  }

updateMemoComponent 更新 memo

  • memo 组件的 props 都是作用于传入的 function 组件内,memo 组件的意义在于进行一次组件包裹,可传入自定义比较函数
  • 创建 child 没有使用调和子节点方式,而是直接创建
  • compare 函数如果没传入,则使用 shallowEqual,返回 true 则跳过更新。
case MemoComponent: {
  const type = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
  return updateMemoComponent(
    current,
    workInProgress,
    type,
    resolvedProps,
    updateExpirationTime,
    renderExpirationTime,
  );
}

function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  updateExpirationTime,
  renderExpirationTime: ExpirationTime,
): null | Fiber {
  // 首次渲染
  if (current === null) {
    // 拿到传入的那个 function component
    let type = Component.type;
    // 如果是纯函数组件没有 contruct、defaultProps 的组件且没有比较函数,则按 SimpleMemoComponent 更新
    if (isSimpleFunctionComponent(type) && Component.compare === null) {
      // If this is a plain function component without default props,
      // and with only the default shallow comparison, we upgrade it
      // to a SimpleMemoComponent to allow fast path updates.
      // 更新 tag,下次更新直接走 updateSimpleMemoComponent
      workInProgress.tag = SimpleMemoComponent;
      workInProgress.type = type;
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        type,
        nextProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    // reconcileChildFibers 会把 props 用于当前组件本身的 children 调和的过程 
    // 而对于 memo 组件的 child 来自于传入的那个 function component ,因此要将 props 传递给 function component,来创建 children
    // 直接创建,而不是调和子节点,
    let child = createFiberFromTypeAndProps(
      Component.type,
      null,
      nextProps,
      null,
      workInProgress.mode,
      renderExpirationTime,
    );
    child.ref = workInProgress.ref;
    child.return = workInProgress;
    workInProgress.child = child;
    return child;
  }
  let currentChild = ((current.child: any): Fiber); // This is always exactly one child
  // 如果没有必要更新
  if (
    updateExpirationTime === NoWork ||
    updateExpirationTime > renderExpirationTime
  ) {
    // This will be the props with resolved defaultProps,
    // unlike current.memoizedProps which will be the unresolved ones.
    const prevProps = currentChild.memoizedProps;
    // Default to shallow comparison
    let compare = Component.compare;
    // compare 有则用,否则为 shallowEqual
    compare = compare !== null ? compare : shallowEqual;
    // 相同则跳过更新
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  let newChild = createWorkInProgress(
    currentChild,
    nextProps,
    renderExpirationTime,
  );
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}

updateSimpleMemoComponent

function updateSimpleMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any, // memo 传入的 function component
  nextProps: any,
  updateExpirationTime,
  renderExpirationTime: ExpirationTime,
): null | Fiber {
  if (
    // 更新渲染
    current !== null && 
    // 优先级比当前低 或者 不需要更新
    (updateExpirationTime === NoWork ||
      updateExpirationTime > renderExpirationTime)
  ) {
    const prevProps = current.memoizedProps;
    // 如果 props 浅相同 并且 ref 相同 则跳过更新
    if (
      shallowEqual(prevProps, nextProps) &&
      current.ref === workInProgress.ref
    ) {
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
  // 如果需要更新,纯 function component 按 function Component 方式更新
  return updateFunctionComponent(
    current,
    workInProgress,
    Component,
    nextProps,
    renderExpirationTime,
  );
}
@lz-lee lz-lee changed the title portalComponent、ForwardRef、Mode组件更新 portalComponent、ForwardRef、Mode 更新 Sep 5, 2019
@lz-lee lz-lee changed the title portalComponent、ForwardRef、Mode 更新 portalComponent、ForwardRef、Mode、memo 更新 Sep 6, 2019
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