好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Vue3源码分析组件挂载初始化props与slots

前情提要

上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例)、setupComponent(初始化组件)、setupRenderEffect(更新副作用)。并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit、provide等的实现。本文我们将继续介绍组件挂载流程中的初始化组件。

本文主要内容

初始化props和slots的主要流程。 如何将传递给组件的属性分发给 props 和attrs(需要被透传的属性)。 用户自己实现了 render 函数,如何对其进行标准化。 标准的插槽需要满足哪些条件。

初始化组件

(1).setupComponent

setupComponent : 这个函数主要用于初始化组件。内部主要调用了 initProps、initSlot 、对于有状态组件还需要调用 setupStatefulComponent 。

function setupComponent(instance) {
  //获取vnode的props(真正传递的props)
  const { props, children } = instance.vnode;
  //判断当前是否是有状态组件组件
  const isStateful = isStatefulComponent(instance);
  //通过传递的真实props和声明的props 分离组件参数
  //组件参数放入props中 其余放入instance.attrs
  //处理了props的default情况等
  initProps(instance, props, isStateful);
  //初始化插槽
  initSlots(instance, children);
  //验证名称是否合法,components中的组件名称是否
  //合法,代理instance.ctx,创建setup函数的ctx,调用setup函数
  //处理得到的结果
  const setupResult = isStateful ? 
        setupStatefulComponent(instance) : undefined;
  return setupResult;
}

isStatefulComponent : 这个主要用于判断是否是有状态组件、还记得 Vue3源码分析(4) 中提到的 ShapeFlag 吗?我们在 createVNode 中会判断 type 的类型、然后设置 shapeFlag 来标识当前创建的虚拟节点类型。因此我们只需要获取组件的vNode、而 vNode 中有 shapeFlag 然后判断他的值,就知道他是不是有状态组件了。

function isStatefulComponent(instance) {
  return instance.vnode.shapeFlag &amp; 
         ShapeFlags.STATEFUL_COMPONENT;
}

(2).initProps

initProps : 在创建组件实例中,我们只对 propsOptions 做了处理、但是 props 和 attrs 目前都还是 null 、所以我们需要区分出来那些是 props 那些是 attrs ,同时有些 propsOptions 中设置了 default 属性,那么我们还需要判断是否传递了这个属性,如果没有传递那么应该用 default 属性中的值、又比如传递了 <Comp yes></Comp>并且声明了props:{yes:Boolean},那么应该将 yes 的值变为 true 。而这些就是在初始化props的时候 完成 的。

function initProps(instance, rawProps, isStateful) {
  //定义需要放入的
  const props = {};
  const attrs = {};
  //attrs.__vInternal = 1
  shared.def(attrs, InternalObjectKey, 1);
  //创建propsDefaults
  instance.propsDefaults = Object.create(null);
  //将真实传递的props分配给instance的props和attrs
  setFullProps(instance, rawProps, props, attrs);
  //遍历normalized(合并和的props)
  for (const key in instance.propsOptions[0]) {
    if (!(key in props)) {
      props[key] = undefined;
    }
  }
  //最后将分配好的props和attrs赋值到instance
  if (isStateful) {
    instance.props = reactivity.shallowReactive(props);
  } else {
    //不存在type.props则让props为attrs
    if (!instance.type.props) {
      instance.props = attrs;
    } else {
      instance.props = props;
    }
  }
  instance.attrs = attrs;
}

setFullProps : 在 Vue3源码分析(5) 中我们详细讲解了 propsOptions ,如果读到这里还是不理解的小伙伴可以跳到上一章再去看看。首先重 propsOptions 中解构到 options 和needCastKeys(需要特殊处理的key)。 options 就是进行标准化后的组件定义的 props 。 遍历真正传递给组件的 props ,拿到 key 去 options 中寻找,如果找到了,表示这个属性是组件需要接受的 props ,进一步判断是否是需要特殊处理的 key 如果不是就可以放入 props 中。 如果是需要特殊处理的key,获取他的值放入 rawCastValues 当中。如果在 options 中没有找到,就判断一下 emitsOptions 中是否有,如果这里面也没有那就可以放入 attrs 中, attrs 就是需要透传到 subTree 上的属性。 最后遍历需要特殊处理的 key 调用 resolvePropValue 对 props 进行最后的处理。

function setFullProps(instance, rawProps, props, attrs) {
  //获取通过mixins和extends合并的props
  const [options, needCastKeys] = instance.propsOptions;
  let hasAttrsChanged = false; //attrs是否发生改变
  let rawCastValues;
  if (rawProps) {
    for (let key in rawProps) {
      //如果key是"ref" "key" "ref_for" "ref_key"
      //"onVnodeBeforeMount" "onVnodeMounted"
      //"onVnodeBeforeUpdate "onVnodeUpdated"
      //"onVnodeBeforeUnmount" "onVnodeUnmounted"
      //那么就跳过
      if (shared.isReservedProp(key)) {
        continue;
      }
      //获取rawProps:{a:1}=>value=1
      const value = rawProps[key];
      let camelKey; //小驼峰式的key
      if (
        options &&
        shared.hasOwn(options, (camelKey = shared.camelize(key)))
      ) {
        //这个key不是含有default属性的
        if (!needCastKeys || !needCastKeys.includes(camelKey)) {
          props[camelKey] = value;
        }
        //props:{"msg":{default:"a"}}
        //含有default属性的放入rawCastValues中
        else {
          (rawCastValues || (rawCastValues = {}))[camelKey] = value;
        }
      }
      //判断当前的key是否是用于emits的
      else if (!isEmitListener(instance.emitsOptions, key)) {
        //不是emit自定义事件的key也不是组件参数那么就是attrs
        if (!(key in attrs) || value !== attrs[key]) {
          attrs[key] = value;
          hasAttrsChanged = true;
        }
      }
    }
  }
  /**
   *
   * 这里涉及到四个属性instance, rawProps, props, attrs
   * instance:是当前组件的实例
   * rawProps:真正传递的props可能含有组件参数props,
   * 标签属性attrs,自定义emit事件
   * props:代表声明并且接受到的props
   * attrs:代表没有声明props也不属于emits属性的属性
   * needCastKeys:代表需要特殊处理的属性
   * 例如props:{msg:{default:"a"}}那么msg会被放入
   * needCastKeys中
   *
   */
  if (needCastKeys) {
    //获取非响应式的props
    const rawCurrentProps = reactivity.toRaw(props);
    const castValues = rawCastValues || {};
    for (let i = 0; i < needCastKeys.length; i++) {
      const key = needCastKeys[i]; //msg
      //对于有default的属性进行重设
      //props:{msg:{default:"a"}}
      props[key] = resolvePropValue(
        options, //合并mixins和extends后的props(定义方)
        rawCurrentProps, //非响应式的props(接受方)
        key, //(含有default)的key "msg"
        //例如传递了"msg":1 定义了:props:{msg:{default:"a"}}
        //castValues[key]=1
        castValues[key],
        instance, //实例
        !shared.hasOwn(castValues, key)
      );
    }
  }
  return hasAttrsChanged;
}

resolvePropValue : 对特殊的key进行处理。 首先从 opt 中判断是否有 default 属性,如果有default属性而且传递的 value 是 undefined 的话表示需要使用默认值,还需要进一步判断,如果传递的不是函数但是声明的是函数,需要将 value 设置为这个函数的返回值。例如:props:{yes:Number,default:(props)=>{}}并且没有向组件传递 yes 这个参数,那么 yes 的值将会是default函数的返回值。 对于 propsOptions 中定义的接受值类型是 Boolean 的,但是又没有传递且没有默认值则设置这个值为 false 。 当然还有<Comp yes></Comp>并且声明了是 Boolean ,则会设置为 true 。

function resolvePropValue(options, props, key, value, instance, isAbsent) {
  //获取{msg:{default:"a"}}中的{default:"a"}
  const opt = options[key];
  if (opt != null) {
    //判断是否有default属性
    const hasDefault = shared.hasOwn(opt, "default");
    //如果定义了default但是没有接受到value值
    if (hasDefault && value === undefined) {
      const defaultValue = opt.default;
      //如果需要接受的类型不是函数,但是接受到了函数
      //看看实例的propsDefaults是否有当前key的值
      //还是没有则调用这个defaultValue函数取得值
      if (opt.type !== Function && shared.isFunction(defaultValue)) {
        const { propsDefaults } = instance;
        if (key in propsDefaults) {
          value = propsDefaults[key];
        } else {
          //包裹是为了在调用这个函数的时候
          //获取当前实例不会出错
          setCurrentInstance(instance);
          value = propsDefaults[key] = defaultValue.call(null, props);
          unsetCurrentInstance();
        }
      }
      //设置为默认值
      else {
        value = defaultValue;
      }
    }
    //需要接受的类型是Boolean
    if (opt[0]) {
      //没有设置默认值,也没有传递这个值则为false
      if (isAbsent && !hasDefault) {
        value = false;
      }
      //<Comp yes></Comp>并且声明了yes则设置为true
      else if (opt[1] && value === "") {
        value = true;
      }
    }
  }
  return value;
}

(3).initSlots

initSlots :还记得在 Vue3源码分析(4) 中我们详细讲解了 normalizeChildren ,他主要用于标准化插槽,给 vNode 的 shapeFlag 加上 ARRAY_CHILDREN 或 TEXT_CHILDREN 或 SLOTS_CHILDREN 的标识,但是并没有添加到实例的 slots 属性上。因为那个时候还没有创建实例,所以我们只能在那时候打上标记,在创建实例之后,也就是现在,在去初始化 slots 。对于 SLOTS_CHILDREN、TEXT_CHILDREN、ARRAY_CHILDREN 分别是在那种情况下添加到 shapeFlag 上的,如果你不了解可能会影响这一段代码的阅读,建议在看看第四小节。因为间隔较远,所以理解起来很困难,这部分的文章主要是阐述整个 Vue3 的运行机制。我们后面的章节还会单独讲解 slots 的实现。 SLOTS_CHILDREN : 首先判断 children._ 是否存在,如果是通过Vue的编译器得到的那么一定会有这个标识,当然,用户自己书写 render 函数也可以自己传递这个标识符。但是大部分用户是不会传递的,所以else分支中就是为了处理这种情况,而对于 children._ 存在的,可以直接把 children 当做实例的slots属性。 _ 标识有三个值STABLE、DYNAMIC、FORWORD这个在第四小节也已经讲过了,就不在重复了。 TEXT_CHILDREN、ARRAY_CHILDREN : 因为 children 不是一个对象,而是数组或字符串或 null ,那么需要将其标准化为对象形式。调用 normalizeVNodeSlots 处理。

function initSlots(instance, children) {
  //判断当前实例的children是否是slots
  if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
    const type = children._; //获取shapeSlots
    //有"_"标识表示是通过compiler编译得到的
    if (type) {
      //如果有SLOTS_CHILDREN标识 表示children就是slots属性
      instance.slots = reactivity.toRaw(children);
      //将_属性变为不可枚举属性
      shared.def(children, "_", type);
    } else {
      /**
       * render(){
       *   return h(Comp,null,{
       *     default:()=>h("div",null),
       *     header:()=>h("div",null)
       *   })
       * }
       * 没有则表示用户自己写了render函数
       * 这个时候用户可能不会添加"_"属性
       * 所以需要对slots进行标准化
       */
      normalizeObjectSlots(children, (instance.slots = {}));
    }
  } else {
    instance.slots = {};
    //如果children为字符串或者null或数组情况
    if (children) {
      normalizeVNodeSlots(instance, children);
    }
  }
  //标识slots为内部属性
  shared.def(instance.slots, InternalObjectKey, 1);
}

我们先来看看到底要标准化成什么样子,其实对于 slots 所有的标准化都是为了,将不标准的形式转化为正常通过编译得到的样子。 我们主要关注 createBlock 的第三个参数对象。通过观察我们可以发现标准化的 slots 应该满足, 一个具名插槽对应一个创建好的 VNode ,我们这个例子只有 default 所以 children 对象中只有 default ; 并且必须由 _withCtx 包裹;(确保上下文,禁止block追踪) 参数必须是一个函数,不能是数组;(提升性能) 函数的返回值必须是一个数组。(标准化) 如果你想自己书写标准的插槽,你就应当满足以上四个条件(我选择模板编译)。

<template>
  <Comp>
    我是插槽内容
  </Comp>
</template>
//编译后
function render(_ctx, _cache) {
  const _component_Comp = _resolveComponent("Comp", true)
  return (_openBlock(),
          _createBlock(_component_Comp, null, {
    default: _withCtx(() => [
      _createTextVNode(" 我是插槽内容 ")
    ]),
    _: 1 /* STABLE */
  }))
}

normalizeObjectSlots : 改造成正常编译后的样子。因为没有 _ 标识,所以不是通过编译得到的,这将不能作为标准形式的 slots ,将其标准化。 对于key以"_"开头或 key为$stable 将不会进行标准化。 判断书写的插槽模板是否是函数,如果是则调用 noramlizeSlot ,如果不是警告用户,应该书写函数形式,同样标准化插槽的 value 然后包装成函数在返回。

const normalizeObjectSlots = (rawSlots, slots, instance) => {
  const ctx = rawSlots._ctx;
  for (const key in rawSlots) {
    //_开头或者$stable跳过
    //这将允许设置不进行标准化的插槽
    if (isInternalKey(key)) continue;
    //获取slots的值
    const value = rawSlots[key];
    //如果value已经是一个函数了,需要包裹withCtx执行
    //进行标准化 都需要改成通过编译的样子
    if (shared.isFunction(value)) {
      //给instance.slots赋值
      slots[key] = normalizeSlot(key, value, ctx);
    }
    /**
     * 用户不写函数,抛出警告,使用函数的性能将会更好
     * render(){
     *   return createVnode(Comp,null,{
     *      default:createVnode('div',null)
     *   })
     * }
     */
    else if (value != null) {
      console.warn(
        `Non-function value encountered for slot "${key}". ` +
          `Prefer function slots for better performance.`
      );
      //经过normalizeSlotValue处理 返回的createVnode一定通过数组包裹
      const normalized = normalizeSlotValue(value);
      slots[key] = () => normalized;
    }
  }
};

normalizeSlot : key 代表的是插槽名称(具名插槽,默认为default), rawSlot 代表返回虚拟节点的函数(rawSlot=()=>createVNode()),所以这个函数本质上是调用 normalizeSlotValue 对虚拟节点进行标准化,然后包裹_withCtx,最后返回经过包裹的虚拟节点。接下来我们先看看 withCtx 执行了什么。

const normalizeSlot = (key, rawSlot, ctx) => {
  //已经经过标准化的slot不需要在进行标准化
  if (rawSlot._n) {
    return rawSlot;
  }
  const normalized = withCtx((...args) => {
    if (getCurrentInstance()) {
      warn(
        `Slot "${key}" invoked outside of the render function: ` +
          `this will not track dependencies used in the slot. ` +
          `Invoke the slot function inside the render function instead.`
      );
    }
    //标准化插槽的值 rawSlot=> default:()=>createVnode('div',null)
    return normalizeSlotValue(rawSlot(...args));
  }, ctx);
  //表示不是经过compiler编译的,是用户自己写的render函数
  normalized._c = false;
  return normalized;
};

withCtx : 将传递的 fn 包裹成 renderFnWithContext 在返回。 在执行 fn 的时候包裹一层 currentRenderInstance ,确保当前的实例不出错。 renderFnWithContext 有以下三个属性: _n :如果有这个属性代表当前函数已经被包裹过了,不应该被重复包裹。 _c : 标识的是当前的插槽是通过编译得到的,还是用户自己写的。 _d : 表示执行 fn 的时候是否需要禁止块跟踪, true 代表禁止块跟踪, false 代表允许块跟踪。

function withCtx(
  fn,
  ctx = getCurrentRenderingInstance(),
  isNonScopedSlot
) {
  if (!ctx) return fn;
  if (fn._n) {
    return fn;
  }
  //设置currentRenderingInstance,通过闭包确保调用fn的时候
  //currentRenderingInstance实例为当前实例
  /**
   * 如果用户调用模板表达式内的插槽
   *  <Button>
   *    <template>
   *      <slot></slot>
   *    </template>
   *  </Button>
   * 可能会扰乱块跟踪,因此默认情况下,禁止块跟踪,当
   * 调用已经编译的插槽时强制跳出(由.d标志指示)。
   * 如果渲染已编译的slot则无需执行此操作、因此
   * 我们在renderSlot中调用renderFnWithContext
   * 时,.d设置为false
   */
  const renderFnWithContext = (...args) => {
    //禁止块追踪,将isBlockTreeEnabled设置为0将会停止追踪
    if (renderFnWithContext._d) {
      setBlockTracking(-1);
    }
    const prevInstance = setCurrentRenderingInstance(ctx);
    const res = fn(...args);
    setCurrentRenderingInstance(prevInstance);
    //开启块追踪
    if (renderFnWithContext._d) {
      setBlockTracking(1);
    }
    return res;
  };
  //如果已经是renderFnWithContext则不需要在包装了
  renderFnWithContext._n = true; //_n表示已经经过renderFnWithContext包装
  renderFnWithContext._c = true; //表示经过compiler编译得到
  //true代表禁止块追踪,false代表开启块追踪
  renderFnWithContext._d = true;
  return renderFnWithContext;
}

normalizeSlotValue : 目前 value 传递的是单个VNode或者是数组类型的 VNode ,我们还需要对返回的所有 VNode 进行标准化。这里主要是为了处理,比如default:()=>"asd",如果是字符串,他显然可以这样写,但是我们需要将字符串变成patch阶段能够处理的 VNode 。

function normalizeSlotValue(value){
  if(shared.isArray(value)){
    return value.map(normalizeVNode)
  }
  return [normalizeVNode(value)] 
}

normalizeVNode : 标准化虚拟节点。 当前虚拟节点是 null、boolean ,这样的值不应该显示在页面当中,创建注释节点。 当前 虚拟节点 是一个 数组 ,需要由 Fragment 包裹。例如下面的写法。

//自己写render函数
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>([
       createVNode('div',null),
       createVNode('div',null)
       ])
    })
  }
}
//如果是正常编译获得的那么应该是

如果是 object ,判断当前节点是否挂载过,挂载过需要克隆节点再返回。例如下面这种情况:

export default{
  render(){
    return createVNode(Comp,null,{
      default:()=>createTextVNode('123')
    })
  }
}

如果是 字符串 或者 number ,创建文本节点即可。例如下面这种情况:

//自己写render函数
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>123
    })
  }
}

function normalizeVNode(child) {
  if (child == null || typeof child === "boolean") {
    //没有child或者没有实质性内容创建注释节点
    return createVNode(Comment);
  } else if (shared.isArray(child)) {
    //用户直接写了一个数组,需要包裹一层Fragment
    return createVNode(Fragment, null, child.slice());
  }
  //如果这个节点已经挂载过了克隆这个节点(复用节点)
  else if (typeof child === "object") {
    return cloneIfMounted(child);
  }
  //string 或者 number
  else {
    return createVNode(Text, null, String(child));
  }
}

到此为止我们就完成了对于对象形式的插槽标准化,并放到了实例的slots属性上。 现在你可以通过访问 slots.default 访问到经过标准化后的虚拟节点了。而我们实际在项目中使用的是 <slot name="default"></slot> ,这个又是怎么渲染到页面上的呢?大胆猜测一下就是根据 name 属性获取到 key 然后到 instance.slots 中去找到这个 虚拟节点 最后挂载到页面就可以了。我们会在讲解 slots 的实现章节详细解释,这里就不过多讲解了。

render(){
  return createVNode(Comp,null,{
    default:createVNode('div')
  })
}
//经过标准化后,相当于
render(){
  return createVNode(Comp,null,{
    default:withCtx(()=>[createVNode('div')])
  })
}
//其他的情况都差不多,都是为了标准化为
//满足上面四个条件的样子

下面我们讲解另一个分支,如果用户用数组或字符串或数字作为 children 参数呢? createVNode(Comp,null,[]) 就像这样。又或者 createVNode(Comp,null,123) 这样。这就是标识为 ARRAY_CHILDREN 或 TEXT_CHILDREN 的情况了,显然调用了 normalizeVNodeSlots 进行处理。 normalizeVNodeSlots :这个情况我们可以把传递的第三个参数看成是调用对象形式的 default函数 的返回值,那么我们只需要标准化第三个参数然后包装成一个函数,赋值给 slots.default 就可以啦。

const normalizeVNodeSlots = (instance, children) => {
  const normalized = normalizeSlotValue(children);
  instance.slots.default = () => normalized;
};

额外内容

在 normalizeVNode 函数中,如果传递的 child 是一个 对象 ,那么调用了 cloneIfMounted ,这个函数是干什么的呢?如果 el 有值,表示已经有 真实的DOM 了,那么就一定调用了 render 函数,也一定 挂载 过元素了。我们看看他是如何克隆节点的呢?

//挂载过的vnode有el属性
function cloneIfMounted(child) {
  return child.el === null || child.memo ? 
         child : cloneVNode(child);
}

cloneVNode : 用于浅克隆一个 VNode 。还可以提供额外的props合并之前 VNode 身上的属性。 如果提供了 extraProps ,调用 mergeProps 合并之前的 props 和新的props。对 key 为class、style的属性做了特殊处理。并且后面的 props 可以覆盖前面的 props 当 key 为 class 的时候,之前的 class 已经经过标准化了一定是一个字符串,我们需要将新的class与之前的 class 合并为一个字符串。 当 key 为 style 的时候,合并新旧的 style 对象。 其余情况,让新的覆盖旧的。

function mergeProps(...args) {
  const ret = {};
  for (let i = 0; i < args.length; i++) {
    const toMerge = args[i];
    for (const key in toMerge) {
      //结合class
      if (key === "class") {
        if (ret.class !== toMerge.class) {
          ret.class = shared.normalizeClass([ret.class, toMerge.class]);
        }
      }
      //结合style属性
      else if (key === "style") {
        ret.style = shared.normalizeStyle([ret.style, toMerge.style]);
      }
      else if (key !== "") {
        ret[key] = toMerge[key];
      }
    }
  }
  return ret;
}

将合并的新 props 作为新的 VNode 的 props 属性。如果传递了 mergeRef 参数,表示需要合并 ref ,那么需要读取 mergeProps 中的 ref属性 进行合并,之前的 ref 可能是数组(使用了v-for加ref),将 最新的ref 添加到数组的后面,不是数组则转化为数组在合并他们 两个ref 到这个数组中。 对于 静态节点 ,需要 深度克隆children 。

function cloneVNode(vnode, extraProps, mergeRef = false) {
  const { props, ref, patchFlag, children } = vnode;
  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
  const cloned = {
    //省略了大量属性,其他的属性和传递的
    //vnode一样,这里只列举了可能被改变的
    key: mergedProps && normalizeKey(mergedProps),
    ref:
      extraProps && extraProps.ref
        ? mergeRef && ref
          ? shared.isArray(ref)
            ? ref.concat(normalizeRef(extraProps))
            : [ref, normalizeRef(extraProps)]
          : normalizeRef(extraProps)
        : ref,
    children:
      patchFlag === PatchFlags.HOISTED && shared.isArray(children)
        ? children.map(deepCloneVNode)
        : children,
    shapeFlag: vnode.shapeFlag,
    patchFlag:
      extraProps && vnode.type !== Fragment
        ? patchFlag === PatchFlags.HOISTED
          ? PatchFlags.FULL_PROPS
          : patchFlag | PatchFlags.FULL_PROPS
        : patchFlag,
  };
  return cloned;
}
function deepCloneVNode(vnode) {
    const cloned = cloneVNode(vnode);
    if (shared.isArray(vnode.children)) {
        cloned.children = vnode.children.map(deepCloneVNode);
    }
    return cloned;
}

总结

本文我们主要介绍了如何对生成的组件实例的props和slots属性进行初始化。 在初始化props中,根据定义组件的props和接受到的props放到 instance.props 中,对于定义了但是没有传递,又有默认值的我们需要使用默认值。当然我们还需要设置透传属性 attrs 的值,如果传递了,但是没有在 props、emits 中定义,那么会认为是透传属性,需要将其放入到 instance.attrs 中。 然后我们详细讲解了slots的初始化。这一部分主要是对用户自己使用 render 函数来渲染的模板,进行标准化保证后续的执行不会出错。 最后我们在额外内容中介绍了 cloneVNode 的 api 实现。 下文中我们将会继续讲解,对于其他组件定义的属性的初始化。也就是 setupStatefulComponent 函数,这里将会对watch、data、computed等属性进行处理,调用setup函数、beforeCreat,created钩子等。

以上就是Vue3源码分析组件挂载初始化props与slots的详细内容,更多关于Vue3组件挂载初始化的资料请关注其它相关文章!

查看更多关于Vue3源码分析组件挂载初始化props与slots的详细内容...

  阅读:85次