最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3組件掛載之創(chuàng)建組件實(shí)例詳解

 更新時(shí)間:2022年10月21日 14:47:35   作者:豬豬愛前端  
這篇文章主要為大家介紹了Vue3組件掛載之創(chuàng)建組件實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前情提要

上文我們講解了執(zhí)行createApp(App).mount('#root')中的mount函數(shù),我們分析了創(chuàng)建虛擬節(jié)點(diǎn)的幾個(gè)方法,以及setRef的執(zhí)行機(jī)制、本文我們繼續(xù)講解mountComponent,掛載組件的流程。

本文主要內(nèi)容

  • createComponentInstance發(fā)生了什么?
  • 如何標(biāo)準(zhǔn)化組件定義的props、emits?
  • 為什么provide提供的值子組件都能訪問到?
  • 組件v-model實(shí)現(xiàn)原理、組件v-model修飾符實(shí)現(xiàn)原理。
  • 組件emit實(shí)現(xiàn)原理。
  • once修飾符實(shí)現(xiàn)原理。
  • 幾乎所有組件實(shí)例屬性的詳細(xì)講解。

mountComponent

  • 這個(gè)方法主要用于掛載組件,根據(jù)傳遞的虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例,調(diào)用setupComponent函數(shù)進(jìn)行初始化組件實(shí)例的插槽props,如果是有狀態(tài)組件還需要處理setup的返回值。最后調(diào)用setupRenderEffect綁定副作用更新函數(shù)。這個(gè)函數(shù)比較簡(jiǎn)單,我們將重心放到createComponentInstance、setupComponent、setupRenderEffect當(dāng)中、后文也是圍繞這三個(gè)方法進(jìn)行依次講解。
const mountComponent = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    //創(chuàng)建組件實(shí)例
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent,
      parentSuspense
    ));
    setupComponent(instance);
    if (instance.asyncDep) {
      //處理異步邏輯,suspense的時(shí)候在進(jìn)行講解
    }
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    );
  };

創(chuàng)建組件實(shí)例

  • createComponentInstance: 根據(jù)虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例的方法,簡(jiǎn)單的說(shuō)就是創(chuàng)建一個(gè)instance對(duì)象,這個(gè)上面有許多的屬性用來(lái)描述當(dāng)前這個(gè)組件以及存儲(chǔ)需要后續(xù)使用到的屬性,所以我們主要講解各個(gè)屬性的作用,我們先來(lái)看看這個(gè)函數(shù)的源碼。
  • uid: 標(biāo)識(shí)當(dāng)前實(shí)例的id,這是一個(gè)從0開始的遞增值,同時(shí)他也是Vue調(diào)度器優(yōu)先級(jí)值越小的值優(yōu)先級(jí)越高,試想一下,父組件先掛載,然后才會(huì)掛載子節(jié)點(diǎn),子節(jié)點(diǎn)如果包含組件,那么uid的值將會(huì)比父組件uid值大,當(dāng)父組件掛載完畢,父組件的兄弟組件開始掛載,那么uid將會(huì)更大,這樣也符合流程更新邏輯,關(guān)于調(diào)度器我們之后單獨(dú)開一章節(jié)來(lái)進(jìn)行講解。
  • vnode: 實(shí)例的虛擬節(jié)點(diǎn)。
  • parent: 當(dāng)前組件實(shí)例的父組件實(shí)例。
  • appContext:createApp的時(shí)候注入的一個(gè)上下文,我們通過app.component,app.mixin調(diào)用后緩存的componentmixin都放在這里面,如果沒有parent表示當(dāng)前是根組件,那么就是這個(gè)appContext,如果有parent則繼承parent的appContext。我們可以通過這里發(fā)現(xiàn)所有的組件的appContext都會(huì)是唯一的一個(gè)context。因此所有組件實(shí)例都將能訪問到全局注冊(cè)的component、mixin、directive等。我們來(lái)回顧一下它的結(jié)構(gòu)(省略了部分屬性)。
{
  app: null,
  config: {
    globalProperties: {},
    optionMergeStrategies: {},
    compilerOptions: {},
  },
  mixins: [],
  components: {}, 
  directives: {}, 
};
  • next:組件更新有兩種形式,一種是組件內(nèi)部的狀態(tài)發(fā)生了改變,引起了組件自身更新;另外一種是父組件傳遞了props,props改變,父組件更新、導(dǎo)致子組件引起了更新。第一種nextnull。如果是第二種,那么需要給next賦值為當(dāng)前子組件最新的VNode(虛擬節(jié)點(diǎn))。而next將會(huì)用于更新組件的props以及slots等屬性。組件自身狀態(tài)改變引起的更新是不需要更新props的。
  • subTree: 調(diào)用render函數(shù)之后返回的組件要渲染的虛擬根節(jié)點(diǎn),可以通過這個(gè)屬性讀取到組件需要渲染的所有虛擬節(jié)點(diǎn)。
  • effect: 在setupRenderEffect中創(chuàng)建的reactiveEffect,我們?cè)?code>Vue3源碼分析(2)中講解了reactiveEffect,不了解的可以在閱讀一下,目前簡(jiǎn)單理解為在調(diào)用render函數(shù)的時(shí)候收集依賴,如果響應(yīng)式的值發(fā)生了改變會(huì)重新調(diào)用update函數(shù)執(zhí)行更新流程。我們將會(huì)在講解setupRenderEffect中詳細(xì)講解這一部分。
  • update: 組件更新函數(shù),只要調(diào)用了這個(gè)函數(shù),組件就會(huì)強(qiáng)制更新。同時(shí)響應(yīng)式發(fā)生改變調(diào)用的也是這個(gè)函數(shù)。
  • render: 渲染函數(shù)??梢酝ㄟ^編譯<template></template>得到,也可以通過寫template屬性獲得,也可以通過自己寫render函數(shù)。
  • exposed: 組件指定的暴露到外部的屬性。
  • provides: 用戶在組件中設(shè)置了provide,子代組件可以通過inject收到傳遞的值,provides:parent ? parent.provides : Object.create(appContext.provides)通過這段代碼分析我們可以知道,provides中至少含有全局提供的provides,如果當(dāng)前組件提供了provide,后面會(huì)將其混合,并且繼承父組件provides,這也就解釋了為什么provides可以向下傳遞,因?yàn)槊恳粚佣伎梢允盏?code>本組件的provides和父組件的provides并進(jìn)行合并。
  • components: 如果你想在template中使用組件,需要在這里注冊(cè),對(duì)于使用了<script setup>語(yǔ)法糖的會(huì)將其編譯components屬性當(dāng)中,當(dāng)然這個(gè)屬性就是用來(lái)存放這些注冊(cè)的組件的
  • directives: 存放自定義指令。
  • propsOptions: 合并了mixins和extends后的props,這里的屬性為什么是propsOptions而不是props呢?這個(gè)VNode中的props又有什么關(guān)系呢?實(shí)際上propsOptions代表的是用戶在組件內(nèi)定義的props,而VNode中的props外部傳遞給組件的props。這一點(diǎn)要加以區(qū)分。同時(shí)這里調(diào)用了normalizePropsOptions來(lái)對(duì)propsOptions進(jìn)行標(biāo)準(zhǔn)化。接下來(lái)我們分析一下normalizePropsOptions函數(shù)。這個(gè)函數(shù)比較長(zhǎng)我們分成三個(gè)部分來(lái)分析。
  • 首先從appContext中獲取props緩存,避免處理過了組件props重復(fù)處理。如果comp不是一個(gè)函數(shù),這個(gè)判斷是因?yàn)椋?code>Vue3中允許函數(shù)組件的存在,但是函數(shù)組件是無(wú)狀態(tài)組件也沒有props,所以不做處理。那么剩下的都是有狀態(tài)組件了,這里會(huì)處理全局注冊(cè)的mixins,組件本身的mixinsextends。我們可以發(fā)現(xiàn)全局的最先處理,所以全局注冊(cè)的mixins優(yōu)先級(jí)將會(huì)是最低的,其次是extends,顯然優(yōu)先級(jí)最高的則是組件自身的mixins,因?yàn)樗詈髨?zhí)行,那么shared.extend將會(huì)最后覆蓋之前的props。我們還可以發(fā)現(xiàn)extends屬性和mixins屬性在實(shí)現(xiàn)上沒有任何區(qū)別,只是mixins可以是數(shù)組,而extends不能是數(shù)組。最后說(shuō)一下asMixin,我們知道全局的mixins只需要合并一次,但是normalizePropsOptions會(huì)調(diào)用多次,為了避免全局屬性混合的多次執(zhí)行,設(shè)置了asMixin這個(gè)參數(shù)。當(dāng)asMixin為true的時(shí)候表示不需要在合并全局的mixins了。 特別提示:shared.extend就是Object.assign。
function normalizePropsOptions(comp, appContext, asMixin = false) {
  //獲取props的緩存
  const cache = appContext.propsCache;
  const cached = cache.get(comp);
  //這個(gè)緩存是一個(gè)type對(duì)應(yīng)一個(gè)[normalized, needCastKeys]
  //normalized表示合并了mixins和extends后的props
  if (cached) {
    return cached;
  }
  const raw = comp.props;
  const normalized = {};
  const needCastKeys = [];
  let hasExtends = false;
  if (!shared.isFunction(comp)) {
    //用于合并props的函數(shù),因?yàn)閑xtends和mixins
    //中還可以寫mixins和extends所以需要遞歸合并
    /**
     * 例如const mixins = [{
     *    extends:{},
     *    mixins:[{props}],
     *    props
     * }]
     */
    const extendProps = (raw) => {
      hasExtends = true;
      const [props, keys] = normalizePropsOptions(raw, appContext, true);
      //normalized為合并后的props
      shared.extend(normalized, props);
      if (keys) needCastKeys.push(...keys);
    };
    //首先合并全局注冊(cè)的mixins中的props屬性(最先合并的優(yōu)先級(jí)最低)
    if (!asMixin && appContext.mixins.length) {
      appContext.mixins.forEach(extendProps);
    }
    //然后合并extends屬性(中間合并的優(yōu)先級(jí)居中)
    //(extends功能與mixins幾乎一樣)但是更注重于繼承
    //并且extends不能是數(shù)組
    if (comp.extends) {
      extendProps(comp.extends);
    }
    //最后合并組件自身的mixins(最后合并的優(yōu)先級(jí)最高)
    if (comp.mixins) {
      comp.mixins.forEach(extendProps);
    }
  }
  //省略第二部分的代碼...
}
  • 我們知道這個(gè)函數(shù)主要是對(duì)propsOptions進(jìn)行標(biāo)準(zhǔn)化,簡(jiǎn)單的說(shuō)就是將各式各樣的propsOptions統(tǒng)一成唯一標(biāo)準(zhǔn)。當(dāng)傳遞的props:['msg']數(shù)組形式的時(shí)候,我們需要將其轉(zhuǎn)化為props:{msg:{}}。validatePropName用于檢測(cè)key是否合法,Vue中,組件傳遞參數(shù)不能以$開頭定義數(shù)據(jù)。例如$msg就是不合法的。
function normalizePropsOptions(comp, appContext, asMixin = false) {
  //省略第一部分的代碼...
  //如果沒有props且沒有全局的mixins
  //組件本身的mixins、extends則設(shè)置
  //當(dāng)前實(shí)例的props緩存為空
  if (!raw && !hasExtends) {
    if (shared.isObject(comp)) {
      cache.set(comp, shared.EMPTY_ARR);
    }
    return shared.EMPTY_ARR;
  }
  //處理這種類型props:['msg','hello']
  if (shared.isArray(raw)) {
    for (let i = 0; i < raw.length; i++) {
      if (!shared.isString(raw[i])) {
        console.warn(
          `props must be strings when using array syntax. ${raw[i]}`
        );
      }
      //將v-data-xxx轉(zhuǎn)化為駝峰式vDataXxx
      //但是不能以$開頭
      const normalizedKey = shared.camelize(raw[i]);
      if (validatePropName(normalizedKey)) {
        //將其變?yōu)閜rops:{"msg":{}}
        normalized[normalizedKey] = shared.EMPTY_OBJ;
      }
    }
  }
  //省略第三部分的代碼...
 }
  • 在上面的代碼中頻繁出現(xiàn)needCastKeys,這個(gè)代表的是需要特殊處理的key,例如:props:{msg:{default:"msg"}}含有default,那么理論上我們應(yīng)當(dāng)判斷傳遞的屬性值是否存在,然后在決定是否使用default的值,但是這里我們僅進(jìn)行標(biāo)準(zhǔn)化,所以對(duì)于含有default屬性的我們需要單獨(dú)放入needCastKeys中,便于后面對(duì)props中的處理。再比如說(shuō)<Comp yes></Comp>傳遞了yes屬性,在propsOptionsprops:{yes:{type:Boolean}}這樣的key=>"yes"也是需要處理的,yes的值應(yīng)該為true,所以對(duì)于type中含有Boolean的也需要放入needCastKeys中。
export function normalizePropsOptions(comp, appContext, asMixin = false) {
  //省略第二部分代碼...
  if (shared.isArray(raw)) {
    //省略...
  }
  //處理props:{msg:String}
  else if (raw) {
    if (!shared.isObject(raw)) {
      warn(`invalid props options`, raw);
    }
    //循環(huán)遍歷所有的key
    for (const key in raw) {
      //"v-data-xxx"=>"vDataXxx"變?yōu)樾●劮迨?
      const normalizedKey = shared.camelize(key);
      //檢驗(yàn)key是否合法
      if (validatePropName(normalizedKey)) {
        const opt = raw[key]; //獲取value
        //如果獲取的value是數(shù)組或函數(shù)轉(zhuǎn)化則為{type:opt}
        //props:{"msg":[]||function(){}}=>
        //props:{"msg":{type:msg的值}}
        const prop = (normalized[normalizedKey] =
          shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt);
        if (prop) {
          //找到Boolean在prop.type中的位置 Boolean,["Boolean"]
          const booleanIndex = getTypeIndex(Boolean, prop.type);
          //找到String在prop.type中的位置
          const stringIndex = getTypeIndex(String, prop.type);
          prop[0] = booleanIndex > -1; //type中是否包含Boolean
          //type中不包含String或者Boolean的位置在String前面
          //例如:"msg":{type:["Boolean","String"]}
          prop[1] = stringIndex < 0 || booleanIndex < stringIndex;
          //如果有default屬性,或者type中包含Boolean放入needCastKeys中
          if (booleanIndex > -1 || shared.hasOwn(prop, "default")) {
            needCastKeys.push(normalizedKey);
          }
        }
      }
    }
  }
  const res = [normalized, needCastKeys];
  //設(shè)置緩存
  if (shared.isObject(comp)) {
    cache.set(comp, res);
  }
  return res; //返回合并后的normalized
}
  • emitsOptions :合并了mixinsextends后的emits屬性。并且對(duì)emits進(jìn)行了標(biāo)準(zhǔn)化。主要是調(diào)用了normalizeEmitsOptions進(jìn)行處理,這個(gè)函數(shù)的邏輯和normlizePropsOptions非常相似,如果你看懂了上述的解釋,這個(gè)函數(shù)相信你很容易就能看懂,這里就不在做多余的解釋了。
export function normalizeEmitsOptions(comp, appContext, asMixin = false) {
  //獲取appContext中的緩存
  const cache = appContext.emitsCache;
  const cached = cache.get(comp);
  //如果已經(jīng)讀取過返回緩存
  if (cached !== undefined) {
    return cached;
  }
  //獲取組件的emits屬性{emits:['']}
  //還可以對(duì)象寫法{emits:{'':null||function(){}}}
  const raw = comp.emits;
  //最終的合并對(duì)象
  let normalized = {};
  let hasExtends = false;
  if (!shared.isFunction(comp)) {
    //合并emits的方法
    const extendEmits = (raw) => {
      const normalizedFromExtend = normalizeEmitsOptions(raw, appContext, true);
      if (normalizedFromExtend) {
        hasExtends = true;
        shared.extend(normalized, normalizedFromExtend);
      }
    };
    //最先合并appContext中的(優(yōu)先級(jí)最低)
    if (!asMixin && appContext.mixins.length) {
      appContext.mixins.forEach(extendEmits);
    }
    //然后合并實(shí)例的extends(優(yōu)先級(jí)居中)
    if (comp.extends) {
      extendEmits(comp.extends);
    }
    //最后合并組件自身的(優(yōu)先級(jí)最高)
    if (comp.mixins) {
      comp.mixins.forEach(extendEmits);
    }
  }
  if (!raw && !hasExtends) {
    //設(shè)置緩存
    if (shared.isObject(comp)) {
      cache.set(comp, null);
    }
    return null;
  }
  //即使emits:[]是數(shù)組最終也會(huì)被轉(zhuǎn)化為對(duì)象
  //emits:['m']=>emits:{'m':null}
  if (shared.isArray(raw)) {
    raw.forEach((key) => (normalized[key] = null));
  } else {
    //合并
    shared.extend(normalized, raw);
  }
  if (shared.isObject(comp)) {
    cache.set(comp, normalized);
  }
  return normalized;
}
  • attrs: 如果你給組件傳遞了沒有在組件內(nèi)部聲明的屬性,那么將會(huì)放入attrs中。例如:
<Comp id="a"></Comp>
//Comp組件 沒有對(duì)id的聲明,那么會(huì)放入attrs中
export default {
  props:{}
}
  • setupState: setup的返回值。
  • ctx: 當(dāng)前組件的上下文
  • 通過createDevRenderContext函數(shù)創(chuàng)建。在這個(gè)函數(shù)當(dāng)中,對(duì)ctx對(duì)象進(jìn)行了代理,可以通過ctx._訪問到組件的實(shí)例,同時(shí)將publicPropertiesMap上所有的屬性代理到了ctx上,簡(jiǎn)單的說(shuō)就是之前需要通過publicPropertiesMap訪問屬性,現(xiàn)在在ctx上同樣能訪問到。這樣的代理方式在Vue中被大量采用,setupState methods data computed watch props當(dāng)中所有的屬性都將會(huì)采用這種方式被代理到ctx上,當(dāng)然會(huì)出現(xiàn)重名問題,所以保證上述的這些屬性中,應(yīng)當(dāng)避免重名,否則會(huì)有警告提示。當(dāng)然這些屬性是是在哪里被代理到ctx上的,我們后面都會(huì)講到。
function createDevRenderContext(instance) {
  const target = {};
  //可通過_訪問實(shí)例對(duì)象
  Object.defineProperty(target, `_`, {
    configurable: true,
    enumerable: false,
    get: () => instance,
  });
  Object.keys(publicPropertiesMap).forEach((key) => {
    Object.defineProperty(target, key, {
      configurable: true,
      enumerable: false,
      get: () => publicPropertiesMap[key](instance),
      set: shared.NOOP,
    });
  });
  return target;
}
  • 不知道這個(gè)這個(gè)對(duì)象上的方法你是否很熟悉呢?沒錯(cuò),這就是Vue官方展示的組合式Api,你可以在setup中訪問this調(diào)用到這些方法,不妨大膽猜測(cè)一下,為什么在setup中訪問this能讀取的這些方法?其實(shí)很簡(jiǎn)單。setup.call(instance.ctx,其他參數(shù)),實(shí)際上只要能訪問到this的地方,基本上都是訪問的instance.ctx、當(dāng)然有些地方的this綁定的是instance.proxy。這兩者的區(qū)別我們?cè)诤竺嬖谶M(jìn)行講解。其中的i代表的是組件實(shí)例。
const publicPropertiesMap = shared.extend(Object.create(null), {
  $: (i) => i, //獲取當(dāng)前實(shí)例
  $el: (i) => i.vnode.el,
  $data: (i) => i.data, //獲取實(shí)例的data
  $props: (i) => reactivity.shallowReadonly(i.props), //獲取props
  $attrs: (i) => reactivity.shallowReadonly(i.attrs),
  $slots: (i) => reactivity.shallowReadonly(i.slots),
  $refs: (i) => reactivity.shallowReadonly(i.refs),
  $emit: (i) => i.emit,
  //獲取options
  $options: (i) => resolveMergedOptions(i),
  //強(qiáng)制更新
  $forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)),
  // $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)),
  $watch: (i) => instanceWatch.bind(i),
  $parent: (i) => getPublicInstance(i.parent),
  $root: (i) => getPublicInstance(i.root),
});
  • emit: 觸發(fā)傳遞給組件的函數(shù)。通過this.$emit調(diào)用的就是這個(gè)函數(shù)。對(duì)于emit這個(gè)方法我們同樣分成三個(gè)部分進(jìn)行講解。
  • 之前我們?cè)敿?xì)講解了emitsOptions,他最終會(huì)被標(biāo)準(zhǔn)化。這也是標(biāo)準(zhǔn)化的作用,后續(xù)的處理都會(huì)變得很簡(jiǎn)單,而不需要兼容多種寫法。如果調(diào)用了emit方法,但是在emitsOptions中沒有這個(gè)屬性,表示并沒有注冊(cè),需要警告用戶,同時(shí)如果你傳遞的emits是一個(gè)函數(shù),那么他就是一個(gè)檢驗(yàn)函數(shù)(如果不理解請(qǐng)查看Vue官網(wǎng)對(duì)于emits的解釋),傳遞的參數(shù)為調(diào)用emits傳遞的剩余參數(shù)
function emit(instance, event, ...rawArgs) {
  //已經(jīng)卸載無(wú)須在執(zhí)行
  if (instance.isUnmounted) return;
  //獲取props
  const props = instance.vnode.props || shared.EMPTY_OBJ;
  //獲取經(jīng)過標(biāo)準(zhǔn)化的emits和props
  //emits:{方法名:null||function(){}}
  //如果為function代表的是驗(yàn)證函數(shù)
  const {
    emitsOptions,
    propsOptions: [propsOptions],
  } = instance;
  if (emitsOptions) {
    //警告用戶:調(diào)用了emit 但是沒有在emitOptions中找到,代表沒有聲明
    if (!(event in emitsOptions)) {
      if (!propsOptions || !(shared.toHandlerKey(event) in propsOptions)) {
        warn(
          `Component emitted event "${event}" but it is neither declared in ` +
            `the emits option nor as an "${shared.toHandlerKey(event)}" prop.`
        );
      }
    }
    //獲取驗(yàn)證函數(shù)
    else {
      const validator = emitsOptions[event];
      if (shared.isFunction(validator)) {
        //調(diào)用驗(yàn)證函數(shù),返回false則警告
        const isValid = validator(...rawArgs);
        if (!isValid) {
          warn(
            `Invalid event arguments: event validation failed for event "${event}".`
          );
        }
      }
    }
  }
  //省略第二部分代碼...
 }
  • 這里的處理可能會(huì)讓你很懵逼,這個(gè)isModelListener是個(gè)啥?modifiersKey又是個(gè)啥?在講解這個(gè)之前你需要了解v-model在組件中的用法(你可以在Vue3官網(wǎng)查詢),以及他們的編譯結(jié)果。通過編譯結(jié)果我們可以發(fā)現(xiàn),新增了屬性modelModifiers,這就是咱們下面訪問的modifiersKey了,同時(shí)v-model指令本質(zhì)上會(huì)被編譯為onUpdate:modelValue,所以我們可以通過判斷prop的開頭是否是onUpdate來(lái)判斷這是不是一個(gè)v-model指令。有了這兩點(diǎn)我們就能理解第二部分的代碼在做什么了。首先判斷當(dāng)前發(fā)射的事件是否是v-model事件,如果是,獲取修飾符對(duì)傳遞的參數(shù)進(jìn)行轉(zhuǎn)換,然后再傳遞給發(fā)射的事件函數(shù)。
<template>
  <Comp v-model.trim.number = "a" />
</template>
//編譯后
function render(_ctx, _cache) {
  const _component_Comp = _resolveComponent("Comp", true)
  return (_openBlock(), _createBlock(_component_Comp, {
    modelValue: _ctx.a,
    "onUpdate:modelValue": $event => ((_ctx.a) = $event),
    modelModifiers: { trim: true,number:true }
  }, null, 8, ["modelValue"]))
}
function emit(instance, event, ...rawArgs) {
  //省略第一部分代碼...
  let args = rawArgs;
  //判斷是否是v-model事件 => update:modelValue
  const isModelListener = event.startsWith("update:");
  const modelArg = isModelListener && event.slice(7); //modelValue
  if (modelArg && modelArg in props) {
    //獲取modifiersKey=>modelModifiers
    const modifiersKey = `${
      modelArg === "modelValue" ? "model" : modelArg
    }Modifiers`;
    //當(dāng)給組件傳遞v-model的時(shí)候
    //<Button v-model.trim="a"></Button>
    //當(dāng)這樣傳遞的時(shí)候會(huì)收到modelModifiers={trim:true}
    const { number, trim } = props[modifiersKey] || shared.EMPTY_OBJ;
    //對(duì)emit('',...args)傳遞給父組件的參數(shù)執(zhí)行trim()
    if (trim) {
      args = rawArgs.map((a) => a.trim());
    }
    if (number) {
      args = rawArgs.map(shared.toNumber);
    }
  }
  //省略第三部分代碼...
}
  • 首先通過event在props中找到需要發(fā)射的事件,如果存在執(zhí)行即可。如果添加了once修飾符,例如<C @close-Model.once="handler"></C>他實(shí)際接收到的屬性是onCloseModelOnce,而如果不寫once修飾符就收到的就是onCloseModel,所以添加了once修飾符的執(zhí)行一次后會(huì)被放入emitted當(dāng)中,以后在觸發(fā)就不會(huì)在執(zhí)行了。
function emit(instance, event, ...rawArgs) {
  //省略第二部分的代碼...
  //這里是簡(jiǎn)單寫法,源碼實(shí)際上對(duì)
  //event這個(gè)字符串做了改造。
  let handler = props[event]
  //如果存在則執(zhí)行
  if (handler) {
    handler.apply(instance,args)
  }
  //如果有once事件,存入emitted,并執(zhí)行,以后不再執(zhí)行
  const onceHandler = props[handlerName + `Once`];
  if (onceHandler) {
    if (!instance.emitted) {
      instance.emitted = {};
    } else if (instance.emitted[handlerName]) {
      return;
    }
    instance.emitted[handlerName] = true;
    handler.apply(instance,args);
  }
}
  • 總結(jié)一下emit函數(shù)的作用: 如果聲明的emits中含有函數(shù),作為檢驗(yàn)函數(shù),檢驗(yàn)不通過警告用戶。然后對(duì)組件使用v-model指令做了處理,主要是對(duì)修飾符的實(shí)現(xiàn)。當(dāng)然在HTML標(biāo)簽中使用修飾符并不是在這里實(shí)現(xiàn)的,這里僅實(shí)現(xiàn)了組件的v-model修飾符。最后調(diào)用傳遞父組件傳遞給子組件的函數(shù),并且把子組件要傳遞的參數(shù)給了父組件。如果含有once修飾符,放入emitted緩存中,只執(zhí)行一次。
  • inheritAttrs :如果給組件傳遞了某些props屬性,但是沒有給組件聲明props、emits、或者事件監(jiān)聽器、那么id屬性將會(huì)透?jìng)鞯?code>subTree節(jié)點(diǎn)上,例如:<Comp id= "com"></Comp>如果subTree是一個(gè)div節(jié)點(diǎn),那么id將會(huì)賦值到這個(gè)div上。當(dāng)然你可以設(shè)置inheritAttrs為false禁止透?jìng)鳌?/li>
  • 存放生命周期的屬性: 我們可以看到下面有一些簡(jiǎn)寫的屬性例如:bc、c、bm等,這些都是生命周期鉤子,在這里需要注意的是,這些屬性的值應(yīng)該是一個(gè)數(shù)組,因?yàn)槿绻脩羰褂昧?code>mixins或者extends這些屬性,那么同一個(gè)生命周期函數(shù)可能會(huì)包含多個(gè),而這些生命周期函數(shù)都應(yīng)該被調(diào)用,所以他們的值應(yīng)當(dāng)是一個(gè)數(shù)組 例如:
export default {
  mixins:[{beforeCreate(){}}]
  beforeCreate(){}
}
//那么bc:[createBefore1,createBefore2]
  • 還有一些其他的屬性,我都放在了注釋當(dāng)中,因?yàn)榭磫卧~意思就能理解,就不額外講解了,當(dāng)然后續(xù)這些屬性都將會(huì)用到,也許現(xiàn)在看這些屬性還是比較片面的。但是通過后面的講解,你將會(huì)逐漸了解這些屬性的作用。
function createComponentInstance(vnode, parent, suspense) {
  const type = vnode.type;
  const appContext = (parent ? parent.appContext : vnode.appContext) || {};
  const instance = {
    uid: uid++, //當(dāng)前實(shí)例的id
    vnode, //當(dāng)前實(shí)例對(duì)應(yīng)的vnode
    type, //當(dāng)前實(shí)例對(duì)應(yīng)的編譯后的.vue生成的對(duì)象
    parent, //當(dāng)前實(shí)例的父實(shí)例
    appContext, //app的上下文包含全局注入的插件,自定義指令等
    root: null, //當(dāng)前組件實(shí)例的根實(shí)例
    //響應(yīng)式觸發(fā)的更新next為null,
    //在更新的過程中父組件調(diào)用了子組件的
    //instance.update會(huì)賦值next為最新組件vnode
    next: null,
    subTree: null, //調(diào)用render函數(shù)后的Vnode(處理了透?jìng)?
    effect: null, //實(shí)例的ReactiveEffect
    update: null, //副作用的scheduler
    scope: new EffectScope(true),
    //template編譯結(jié)果或setup返回值為函數(shù)
    //或.vue文件寫的template編譯為的render函數(shù)
    render: null, //渲染函數(shù)
    proxy: null, //代理后的ctx
    exposed: null, //調(diào)用了ctx.expose()方法(限制暴露的數(shù)據(jù))
    exposeProxy: null, //調(diào)用了getExposeProxy方法后的expose
    withProxy: null,
    //當(dāng)前組件的provides,父實(shí)例有則讀取父實(shí)例的否則讀取app上的
    //父組件的provides后續(xù)會(huì)掛載到prototype上,重新賦值當(dāng)前真實(shí)
    //的provide上,這樣可以通過原型鏈訪問到所有上代組件中的provide
    provides: parent ? parent.provides : Object.create(appContext.provides),
    accessCache: null,
    renderCache: [],
    components: null, //當(dāng)前組件的可用組件
    directives: null, //當(dāng)前組件的自定義指令
    //合并mixins和extends中的props屬性
    propsOptions: normalizePropsOptions(type, appContext),
    //合并mixins和extends中的emits屬性
    emitsOptions: normalizeEmitsOptions(type, appContext),
    emit: null, //當(dāng)前實(shí)例調(diào)用emit的函數(shù)
    emitted: null, //含有once修飾符的,執(zhí)行一次后放入這里不再執(zhí)行
    propsDefaults: {}, //默認(rèn)props
    //是否透?jìng)鱝ttrs
    inheritAttrs: type.inheritAttrs,
    // state
    ctx: {}, //當(dāng)前實(shí)例的上下文也就是this
    data: {}, //data函數(shù)返回的值,被代理后才放入
    props: {}, //接受到的組件屬性
    attrs: {}, //接受到的標(biāo)簽屬性
    slots: {}, //組件傳遞的插槽內(nèi)容
    refs: {}, //存入的refs
    setupState: {}, //setup的返回值
    //expose attrs slots emit
    setupContext: null, //傳遞給setup的ctx(只有四個(gè)屬性)
    suspense,
    suspenseId: suspense ? suspense.pendingId : 0,
    asyncDep: null, //setup使用了async修飾符 返回的promise保存在這里
    asyncResolved: false,
    isMounted: false, //是否掛載
    isUnmounted: false, //是否卸載
    isDeactivated: false,
    bc: null, //beforeCreate
    c: null, //create
    bm: null, //beforeMount
    m: null, //mount
    bu: null, //beforeUpdate
    u: null, //update
    um: null, //unmount
    bum: null, //beforeUnmount
    //若組件實(shí)例是 <KeepAlive> 緩存樹的一部分,
    //當(dāng)組件從 DOM 中被移除時(shí)調(diào)用。deactivated
    da: null,
    //若組件實(shí)例是 <KeepAlive> 緩存樹的一部分,
    //當(dāng)組件被插入到 DOM 中時(shí)調(diào)用。activated
    a: null,
    //在一個(gè)響應(yīng)式依賴被組件觸發(fā)了重新渲染之后調(diào)用。
    //renderTriggered
    rtg: null,
    //在一個(gè)響應(yīng)式依賴被組件的渲染作用追蹤后調(diào)用。
    //renderTracked
    rtc: null,
    /**
     * 錯(cuò)誤捕獲鉤子
     * 組件渲染
     * 事件處理器
     * 生命周期鉤子
     * setup() 函數(shù)
     * 偵聽器
     * 自定義指令鉤子
     * 過渡鉤子
     * 錯(cuò)誤捕獲鉤子
     */
    ec: null, //errorHandler
    sp: null, //serverPrefetch
  };
  //創(chuàng)建實(shí)例的上下文
  instance.ctx = createDevRenderContext(instance);
  //當(dāng)前實(shí)例的根實(shí)例
  instance.root = parent ? parent.root : instance;
  instance.emit = emit.bind(null, instance);
  if (vnode.ce) {
    vnode.ce(instance);
  }
  return instance;
}

總結(jié)

  • 掛載組件一共執(zhí)行了三個(gè)主要的函數(shù): createInstanceComponent(創(chuàng)建組件實(shí)例)、setupComponent(初始化組件)、setupRenderEffect(設(shè)置更新副作用)。
  • 本文我們重點(diǎn)講解了創(chuàng)建組件實(shí)例,以及對(duì)幾乎所有的屬性進(jìn)行了詳細(xì)的講解,當(dāng)然suspense相關(guān)屬性我們會(huì)在講解suspense的時(shí)候詳細(xì)為大家剖析。我們還重點(diǎn)講解了ctx對(duì)象,其實(shí)觀察編譯結(jié)果,所有我們?cè)?code>template中訪問的xxx變量都會(huì)變成ctx.xxx,本質(zhì)上就是將所有的可能用到的變量都代理到了ctx上,代理的方式就是Object.defineProperty。當(dāng)然還有如何標(biāo)準(zhǔn)化propsOptions、emitsOptions、同時(shí)還詳細(xì)講解了組件的emit如何實(shí)現(xiàn)。以及在組件中使用v-model和它的修飾符trim、number的實(shí)現(xiàn)過程,once修飾符的實(shí)現(xiàn)原理。
  • 下一小節(jié)我們將繼續(xù)剖析組件掛載的第二個(gè)流程-初始化組件。

以上就是Vue3組件掛載之創(chuàng)建組件實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3 組件掛載創(chuàng)建實(shí)例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue.js常用指令的使用小結(jié)

    Vue.js常用指令的使用小結(jié)

    這篇文章主要介紹了Vue.js常用指令的使用,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-06-06
  • vue項(xiàng)目啟動(dòng)后沒有局域網(wǎng)地址問題

    vue項(xiàng)目啟動(dòng)后沒有局域網(wǎng)地址問題

    這篇文章主要介紹了vue項(xiàng)目啟動(dòng)后沒有局域網(wǎng)地址問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • vue跳轉(zhuǎn)外部鏈接始終有l(wèi)ocalhost的問題

    vue跳轉(zhuǎn)外部鏈接始終有l(wèi)ocalhost的問題

    這篇文章主要介紹了vue跳轉(zhuǎn)外部鏈接始終有l(wèi)ocalhost的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Vue實(shí)現(xiàn)boradcast和dispatch的示例

    Vue實(shí)現(xiàn)boradcast和dispatch的示例

    這篇文章主要介紹了Vue實(shí)現(xiàn)boradcast和dispatch的示例,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下
    2020-11-11
  • vue中如何去掉空格的方法實(shí)現(xiàn)

    vue中如何去掉空格的方法實(shí)現(xiàn)

    這篇文章主要介紹了vue中如何去掉空格的方法實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2018-11-11
  • 如何使用RoughViz可視化Vue.js中的草繪圖表

    如何使用RoughViz可視化Vue.js中的草繪圖表

    這篇文章主要介紹了如何使用RoughViz可視化Vue.js中的草繪圖表,幫助大家更好的理解和使用roughViz,感興趣的朋友可以了解下
    2021-01-01
  • vue路由緩存的幾種實(shí)現(xiàn)方式小結(jié)

    vue路由緩存的幾種實(shí)現(xiàn)方式小結(jié)

    這篇文章主要介紹了vue路由緩存的幾種實(shí)現(xiàn)方式,結(jié)合實(shí)例形式詳細(xì)分析了vue.js路由緩存常見實(shí)現(xiàn)方式、使用技巧與操作注意事項(xiàng),需要的朋友可以參考下
    2020-02-02
  • Vue中Rule的使用方式

    Vue中Rule的使用方式

    這篇文章主要介紹了Vue中Rule的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • 部署Vue項(xiàng)目到服務(wù)器后404錯(cuò)誤的原因及解決方案

    部署Vue項(xiàng)目到服務(wù)器后404錯(cuò)誤的原因及解決方案

    文章介紹了Vue項(xiàng)目部署步驟以及404錯(cuò)誤的解決方案,部署步驟包括構(gòu)建項(xiàng)目、上傳文件、配置Web服務(wù)器、重啟Nginx和訪問域名,404錯(cuò)誤通常是由于歷史模式問題導(dǎo)致的,解決方法是修改Nginx配置,將所有頁(yè)面請(qǐng)求重定向到index.html,并在Vue應(yīng)用中覆蓋所有路由情況
    2025-02-02
  • 關(guān)于this.$refs獲取不到dom的可能原因及解決方法

    關(guān)于this.$refs獲取不到dom的可能原因及解決方法

    這篇文章主要介紹了關(guān)于this.$refs獲取不到dom的可能原因及解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11

最新評(píng)論

东乡族自治县| 萍乡市| 小金县| 克什克腾旗| 宁河县| 武山县| 溧阳市| 德保县| 株洲县| 景宁| 云阳县| 郓城县| 太保市| 洪泽县| 托克托县| 禹州市| 正定县| 华容县| 山西省| 黄浦区| 淮阳县| 洛宁县| 苍溪县| 卓尼县| 交城县| 林西县| 荥经县| 芜湖市| 托里县| 诏安县| 启东市| 四平市| 汉中市| 昌都县| 五大连池市| 师宗县| 广丰县| 盱眙县| 浪卡子县| 绩溪县| 呼图壁县|