当前位置 : 主页 > 网络编程 > JavaScript >

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

来源:互联网 收集:自由互联 发布时间:2023-02-01
目录 前情提要 初始化组件 (1).setupComponent (2).initProps (3).initSlots 额外内容 总结 前情提要 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例)、setupComponent(初
目录
  • 前情提要
  • 初始化组件
    • (1).setupComponent
    • (2).initProps
    • (3).initSlots
  • 额外内容
    • 总结

      前情提要

      • 上文我们分析了挂载组件主要调用了三个函数: 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 & 
               ShapeFlags.STATEFUL_COMPONENT;
      }
      

      (2).initProps

      • initProps: 在创建组件实例中,我们只对propsOptions做了处理、但是propsattrs目前都还是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,拿到keyoptions中寻找,如果找到了,表示这个属性是组件需要接受的props,进一步判断是否是需要特殊处理的key如果不是就可以放入props中。
      • 如果是需要特殊处理的key,获取他的值放入rawCastValues当中。如果在options中没有找到,就判断一下emitsOptions中是否有,如果这里面也没有那就可以放入attrs中,attrs就是需要透传到subTree上的属性。
      • 最后遍历需要特殊处理的key调用resolvePropValueprops进行最后的处理。
      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属性而且传递的valueundefined的话表示需要使用默认值,还需要进一步判断,如果传递的不是函数但是声明的是函数,需要将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,他主要用于标准化插槽,给vNodeshapeFlag加上ARRAY_CHILDRENTEXT_CHILDRENSLOTS_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_CHILDRENTEXT_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
      • keyclass的时候,之前的class已经经过标准化了一定是一个字符串,我们需要将新的class与之前的class合并为一个字符串。
      • keystyle的时候,合并新旧的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作为新的VNodeprops属性。如果传递了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函数来渲染的模板,进行标准化保证后续的执行不会出错。
      • 最后我们在额外内容中介绍了cloneVNodeapi实现。
      • 下文中我们将会继续讲解,对于其他组件定义的属性的初始化。也就是setupStatefulComponent函数,这里将会对watch、data、computed等属性进行处理,调用setup函数、beforeCreat,created钩子等。

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

      网友评论