# 自定义组件的处理

先看一下这一大段代码

const processComponent = (
  n1: VNode | null,
  n2: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  optimized: boolean
) => {
  if (n1 == null) {
    if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
      (parentComponent!.ctx as KeepAliveContext).activate(
        n2,
        container,
        anchor,
        isSVG,
        optimized
      );
    } else {
      mountComponent(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      );
    }
  } else {
    updateComponent(n1, n2, optimized);
  }
};

const mountComponent: MountComponentFn = (
  initialVNode,
  container,
  anchor,
  parentComponent,
  parentSuspense,
  isSVG,
  optimized
) => {
  const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
    initialVNode,
    parentComponent,
    parentSuspense
  ));

  // inject renderer internals for keepAlive
  if (isKeepAlive(initialVNode)) {
    (instance.ctx as KeepAliveContext).renderer = internals;
  }

  setupComponent(instance);

  // setup() is async. This component relies on async logic to be resolved
  // before proceeding
  if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
    if (!parentSuspense) {
      if (__DEV__) warn("async setup() is used without a suspense boundary!");
      return;
    }

    parentSuspense.registerDep(instance, setupRenderEffect);

    // Give it a placeholder if this is not hydration
    if (!initialVNode.el) {
      const placeholder = (instance.subTree = createVNode(Comment));
      processCommentNode(null, placeholder, container!, anchor);
    }
    return;
  }

  setupRenderEffect(
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  );
};

说实话这个函数命名方式还有调用方式。。。是真的很像 react。

我们直接看mountComponentupdatemount大致上类似,当然会有区别,我们后续再讲。

第一步创建实例:

const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
  initialVNode,
  parentComponent,
  parentSuspense
));

关于实例请参考附录:组件实例

创建完实例之后,则调用setupComponent(instance),这里就是根 vue3 中才加入的setup方法有关了,很重要哦。

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR;

  const { props, children, shapeFlag } = instance.vnode;
  const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT;
  initProps(instance, props, isStateful, isSSR);
  initSlots(instance, children);

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined;
  isInSSRComponentSetup = false;
  return setupResult;
}

# 处理 props

首先调用了initProps,那么这里 init 做了啥呢?

  • 根据你组件的props声明来把属性放到 props 或者 attrs 里面(让我觉得很没必要的设计,意义不明)
  • 根据你声明的 props 来进行校验

函数调用链:initProps -> setFullProps -> normalizePropsOptions <-> normalizePropsOptions

这一系列的方法调用就留给各位自己去看了,这真的就是细节了,没啥能讲的,简单概括一下做了哪些事情

setFullProps 里面根据最终得到的[props, attrs]然后判断是否是函数组件,如果是函数组件并且没有声明props那么把所有 attrs 作为 props:

if (isStateful) {
  // stateful
  instance.props = isSSR ? props : shallowReactive(props);
} else {
  if (!instance.type.props) {
    // functional w/ optional props, props === attrs
    instance.props = attrs;
  } else {
    // functional w/ declared props
    instance.props = props;
  }
}

normalizePropsOptions 里面要把extendsmixins里面可能存在的所有 props 进行合并:

let hasExtends = false;
if (__FEATURE_OPTIONS__ && !isFunction(comp)) {
  const extendProps = (raw: ComponentOptions) => {
    const [props, keys] = normalizePropsOptions(raw);
    extend(normalized, props);
    if (keys) needCastKeys.push(...keys);
  };
  if (comp.extends) {
    hasExtends = true;
    extendProps(comp.extends);
  }
  if (comp.mixins) {
    hasExtends = true;
    comp.mixins.forEach(extendProps);
  }
}

这里还有一个needCastKeys的概念,啥意思呢

if (prop) {
  const booleanIndex = getTypeIndex(Boolean, prop.type);
  const stringIndex = getTypeIndex(String, prop.type);
  prop[BooleanFlags.shouldCast] = booleanIndex > -1;
  prop[BooleanFlags.shouldCastTrue] =
    stringIndex < 0 || booleanIndex < stringIndex;
  // if the prop needs boolean casting or default value
  if (booleanIndex > -1 || hasOwn(prop, "default")) {
    needCastKeys.push(normalizedKey);
  }
}

不太好解释,就是对于一些情况需要构造 props

联系 setFullProps 里面的操作来看可能更好理解:

if (needCastKeys) {
  const rawCurrentProps = toRaw(props);
  for (let i = 0; i < needCastKeys.length; i++) {
    const key = needCastKeys[i];
    props[key] = resolvePropValue(
      options!,
      rawCurrentProps,
      key,
      rawCurrentProps[key]
    );
  }
}

对于我们的声明的 props,我们可以增加像default这样的关键字,在我们没有传递的时候使用默认值,或者像boolean类型的 props,如果没有传递那么undefined类型不匹配,就默认给 false, resolvePropValue方法的内容也证明了我这个观点

setFullProps 会根据props声明来区分 props 和 attrs

for (const key in rawProps) {
  const value = rawProps[key];
  if (isReservedProp(key)) {
    continue;
  }
  let camelKey;
  if (options && hasOwn(options, (camelKey = camelize(key)))) {
    props[camelKey] = value;
  } else if (!isEmitListener(instance.type, key)) {
    attrs[key] = value;
  }
}

props 先讲到这里,组件的处理内容较多,篇幅原因就把剩下放到下一章讲解