lz-lee / React-Source-Code

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

portalComponent、ForwardRef、Mode、memo 更新 #10

Open lz-lee opened 5 years ago

lz-lee commented 5 years ago

portalComponent

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,
  };
}
// 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

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 组件

typeofMode

export type TypeOfMode = number;

export const NoContext = 0b000;
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
// 
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 节点

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()

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

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 

```js
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,
  );
}