亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前情提要

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

本文主要內(nèi)容

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

mountComponent

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

創(chuàng)建組件實例

  • createComponentInstance: 根據(jù)虛擬節(jié)點創(chuàng)建組件實例的方法,簡單的說就是創(chuàng)建一個instance對象,這個上面有許多的屬性用來描述當(dāng)前這個組件以及存儲需要后續(xù)使用到的屬性,所以我們主要講解各個屬性的作用,我們先來看看這個函數(shù)的源碼。
  • uid: 標(biāo)識當(dāng)前實例的id,這是一個從0開始的遞增值,同時他也是Vue調(diào)度器優(yōu)先級值,越小的值優(yōu)先級越高,試想一下,父組件先掛載,然后才會掛載子節(jié)點,子節(jié)點如果包含組件,那么uid的值將會比父組件uid值大,當(dāng)父組件掛載完畢,父組件的兄弟組件開始掛載,那么uid將會更大,這樣也符合流程更新邏輯,關(guān)于調(diào)度器我們之后單獨開一章節(jié)來進行講解。
  • vnode: 實例的虛擬節(jié)點。
  • parent: 當(dāng)前組件實例的父組件實例。
  • appContext:createApp的時候注入的一個上下文,我們通過app.component,app.mixin調(diào)用后緩存的componentmixin都放在這里面,如果沒有parent表示當(dāng)前是根組件,那么就是這個appContext,如果有parent則繼承parent的appContext。我們可以通過這里發(fā)現(xiàn)所有的組件的appContext都會是唯一的一個context。因此所有組件實例都將能訪問到全局注冊的component、mixin、directive等。我們來回顧一下它的結(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é)點)。而next將會用于更新組件的props以及slots等屬性。組件自身狀態(tài)改變引起的更新是不需要更新props的。
  • subTree: 調(diào)用render函數(shù)之后返回的組件要渲染的虛擬根節(jié)點,可以通過這個屬性讀取到組件需要渲染的所有虛擬節(jié)點。
  • effect: 在setupRenderEffect中創(chuàng)建的reactiveEffect,我們在Vue3源碼分析(2)中講解了reactiveEffect,不了解的可以在閱讀一下,目前簡單理解為在調(diào)用render函數(shù)的時候收集依賴,如果響應(yīng)式的值發(fā)生了改變會重新調(diào)用update函數(shù)執(zhí)行更新流程。我們將會在講解setupRenderEffect中詳細講解這一部分。
  • update: 組件更新函數(shù),只要調(diào)用了這個函數(shù),組件就會強制更新。同時響應(yīng)式發(fā)生改變調(diào)用的也是這個函數(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,后面會將其混合,并且繼承父組件provides,這也就解釋了為什么provides可以向下傳遞,因為每一層都可以收到本組件的provides父組件的provides并進行合并
  • components: 如果你想在template中使用組件,需要在這里注冊,對于使用了<script setup>語法糖的會將其編譯components屬性當(dāng)中,當(dāng)然這個屬性就是用來存放這些注冊的組件的。
  • directives: 存放自定義指令。
  • propsOptions: 合并了mixins和extends后的props,這里的屬性為什么是propsOptions而不是props呢?這個VNode中的props又有什么關(guān)系呢?實際上propsOptions代表的是用戶在組件內(nèi)定義的props,而VNode中的props外部傳遞給組件的props。這一點要加以區(qū)分。同時這里調(diào)用了normalizePropsOptions來對propsOptions進行標(biāo)準(zhǔn)化。接下來我們分析一下normalizePropsOptions函數(shù)。這個函數(shù)比較長我們分成三個部分來分析。
  • 首先從appContext中獲取props緩存,避免處理過了組件props重復(fù)處理。如果comp不是一個函數(shù),這個判斷是因為,Vue3中允許函數(shù)組件的存在,但是函數(shù)組件是無狀態(tài)組件也沒有props,所以不做處理。那么剩下的都是有狀態(tài)組件了,這里會處理全局注冊的mixins,組件本身的mixinsextends。我們可以發(fā)現(xiàn)全局的最先處理,所以全局注冊的mixins優(yōu)先級將會是最低的,其次是extends,顯然優(yōu)先級最高的則是組件自身的mixins,因為他最后執(zhí)行,那么shared.extend將會最后覆蓋之前的props。我們還可以發(fā)現(xiàn)extends屬性和mixins屬性在實現(xiàn)上沒有任何區(qū)別,只是mixins可以是數(shù)組,而extends不能是數(shù)組。最后說一下asMixin,我們知道全局的mixins只需要合并一次,但是normalizePropsOptions會調(diào)用多次,為了避免全局屬性混合的多次執(zhí)行,設(shè)置了asMixin這個參數(shù)。當(dāng)asMixin為true的時候表示不需要在合并全局的mixins了。 特別提示:shared.extend就是Object.assign。
function normalizePropsOptions(comp, appContext, asMixin = false) {
  //獲取props的緩存
  const cache = appContext.propsCache;
  const cached = cache.get(comp);
  //這個緩存是一個type對應(yīng)一個[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ù),因為extends和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);
    };
    //首先合并全局注冊的mixins中的props屬性(最先合并的優(yōu)先級最低)
    if (!asMixin && appContext.mixins.length) {
      appContext.mixins.forEach(extendProps);
    }
    //然后合并extends屬性(中間合并的優(yōu)先級居中)
    //(extends功能與mixins幾乎一樣)但是更注重于繼承
    //并且extends不能是數(shù)組
    if (comp.extends) {
      extendProps(comp.extends);
    }
    //最后合并組件自身的mixins(最后合并的優(yōu)先級最高)
    if (comp.mixins) {
      comp.mixins.forEach(extendProps);
    }
  }
  //省略第二部分的代碼...
}
  • 我們知道這個函數(shù)主要是對propsOptions進行標(biāo)準(zhǔn)化,簡單的說就是將各式各樣的propsOptions統(tǒng)一成唯一標(biāo)準(zhǔn)。當(dāng)傳遞的props:['msg']數(shù)組形式的時候,我們需要將其轉(zhuǎn)化為props:{msg:{}}。validatePropName用于檢測key是否合法,Vue中,組件傳遞參數(shù)不能以$開頭定義數(shù)據(jù)。例如$msg就是不合法的。
function normalizePropsOptions(comp, appContext, asMixin = false) {
  //省略第一部分的代碼...
  //如果沒有props且沒有全局的mixins
  //組件本身的mixins、extends則設(shè)置
  //當(dāng)前實例的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,這個代表的是需要特殊處理的key,例如:props:{msg:{default:"msg"}}含有default,那么理論上我們應(yīng)當(dāng)判斷傳遞的屬性值是否存在,然后在決定是否使用default的值,但是這里我們僅進行標(biāo)準(zhǔn)化,所以對于含有default屬性的我們需要單獨放入needCastKeys中,便于后面對props中的處理。再比如說<Comp yes></Comp>傳遞了yes屬性,在propsOptionsprops:{yes:{type:Boolean}}這樣的key=>"yes"也是需要處理的,yes的值應(yīng)該為true,所以對于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);
      //檢驗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屬性。并且對emits進行了標(biāo)準(zhǔn)化。主要是調(diào)用了normalizeEmitsOptions進行處理,這個函數(shù)的邏輯和normlizePropsOptions非常相似,如果你看懂了上述的解釋,這個函數(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:['']}
  //還可以對象寫法{emits:{'':null||function(){}}}
  const raw = comp.emits;
  //最終的合并對象
  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)先級最低)
    if (!asMixin && appContext.mixins.length) {
      appContext.mixins.forEach(extendEmits);
    }
    //然后合并實例的extends(優(yōu)先級居中)
    if (comp.extends) {
      extendEmits(comp.extends);
    }
    //最后合并組件自身的(優(yōu)先級最高)
    if (comp.mixins) {
      comp.mixins.forEach(extendEmits);
    }
  }
  if (!raw && !hasExtends) {
    //設(shè)置緩存
    if (shared.isObject(comp)) {
      cache.set(comp, null);
    }
    return null;
  }
  //即使emits:[]是數(shù)組最終也會被轉(zhuǎn)化為對象
  //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)部聲明的屬性,那么將會放入attrs中。例如:
<Comp id="a"></Comp>
//Comp組件 沒有對id的聲明,那么會放入attrs中
export default {
  props:{}
}
  • setupState: setup的返回值。
  • ctx: 當(dāng)前組件的上下文。
  • 通過createDevRenderContext函數(shù)創(chuàng)建。在這個函數(shù)當(dāng)中,對ctx對象進行了代理,可以通過ctx._訪問到組件的實例,同時將publicPropertiesMap上所有的屬性代理到了ctx上,簡單的說就是之前需要通過publicPropertiesMap訪問屬性,現(xiàn)在在ctx上同樣能訪問到。這樣的代理方式在Vue中被大量采用,setupState methods data computed watch props當(dāng)中所有的屬性都將會采用這種方式被代理到ctx上,當(dāng)然會出現(xiàn)重名問題,所以保證上述的這些屬性中,應(yīng)當(dāng)避免重名,否則會有警告提示。當(dāng)然這些屬性是是在哪里被代理到ctx上的,我們后面都會講到。
function createDevRenderContext(instance) {
  const target = {};
  //可通過_訪問實例對象
  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;
}
  • 不知道這個這個對象上的方法你是否很熟悉呢?沒錯,這就是Vue官方展示的組合式Api,你可以在setup中訪問this調(diào)用到這些方法,不妨大膽猜測一下,為什么在setup中訪問this能讀取的這些方法?其實很簡單。setup.call(instance.ctx,其他參數(shù)),實際上只要能訪問到this的地方,基本上都是訪問的instance.ctx、當(dāng)然有些地方的this綁定的是instance.proxy。這兩者的區(qū)別我們在后面在進行講解。其中的i代表的是組件實例。
const publicPropertiesMap = shared.extend(Object.create(null), {
  $: (i) => i, //獲取當(dāng)前實例
  $el: (i) => i.vnode.el,
  $data: (i) => i.data, //獲取實例的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),
  //強制更新
  $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)用的就是這個函數(shù)。對于emit這個方法我們同樣分成三個部分進行講解。
  • 之前我們詳細講解了emitsOptions,他最終會被標(biāo)準(zhǔn)化。這也是標(biāo)準(zhǔn)化的作用,后續(xù)的處理都會變得很簡單,而不需要兼容多種寫法。如果調(diào)用了emit方法,但是在emitsOptions中沒有這個屬性,表示并沒有注冊,需要警告用戶,同時如果你傳遞的emits是一個函數(shù),那么他就是一個檢驗函數(shù)(如果不理解請查看Vue官網(wǎng)對于emits的解釋),傳遞的參數(shù)為調(diào)用emits傳遞的剩余參數(shù)。
function emit(instance, event, ...rawArgs) {
  //已經(jīng)卸載無須在執(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代表的是驗證函數(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.`
        );
      }
    }
    //獲取驗證函數(shù)
    else {
      const validator = emitsOptions[event];
      if (shared.isFunction(validator)) {
        //調(diào)用驗證函數(shù),返回false則警告
        const isValid = validator(...rawArgs);
        if (!isValid) {
          warn(
            `Invalid event arguments: event validation failed for event "${event}".`
          );
        }
      }
    }
  }
  //省略第二部分代碼...
 }
  • 這里的處理可能會讓你很懵逼,這個isModelListener是個啥?modifiersKey又是個啥?在講解這個之前你需要了解v-model在組件中的用法(你可以在Vue3官網(wǎng)查詢),以及他們的編譯結(jié)果。通過編譯結(jié)果我們可以發(fā)現(xiàn),新增了屬性modelModifiers,這就是咱們下面訪問的modifiersKey了,同時v-model指令本質(zhì)上會被編譯為onUpdate:modelValue,所以我們可以通過判斷prop的開頭是否是onUpdate來判斷這是不是一個v-model指令。有了這兩點我們就能理解第二部分的代碼在做什么了。首先判斷當(dāng)前發(fā)射的事件是否是v-model事件,如果是,獲取修飾符對傳遞的參數(shù)進行轉(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的時候
    //<Button v-model.trim="a"></Button>
    //當(dāng)這樣傳遞的時候會收到modelModifiers={trim:true}
    const { number, trim } = props[modifiersKey] || shared.EMPTY_OBJ;
    //對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>他實際接收到的屬性是onCloseModelOnce,而如果不寫once修飾符就收到的就是onCloseModel,所以添加了once修飾符的執(zhí)行一次后會被放入emitted當(dāng)中,以后在觸發(fā)就不會在執(zhí)行了。
function emit(instance, event, ...rawArgs) {
  //省略第二部分的代碼...
  //這里是簡單寫法,源碼實際上對
  //event這個字符串做了改造。
  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ù),作為檢驗函數(shù),檢驗不通過警告用戶。然后對組件使用v-model指令做了處理,主要是對修飾符的實現(xiàn)。當(dāng)然在HTML標(biāo)簽中使用修飾符并不是在這里實現(xiàn)的,這里僅實現(xiàn)了組件的v-model修飾符。最后調(diào)用傳遞父組件傳遞給子組件的函數(shù),并且把子組件要傳遞的參數(shù)給了父組件。如果含有once修飾符,放入emitted緩存中,只執(zhí)行一次。
  • inheritAttrs :如果給組件傳遞了某些props屬性,但是沒有給組件聲明props、emits、或者事件監(jiān)聽器、那么id屬性將會透傳到subTree節(jié)點上,例如:<Comp id= "com"></Comp>如果subTree是一個div節(jié)點,那么id將會賦值到這個div上。當(dāng)然你可以設(shè)置inheritAttrs為false禁止透傳。
  • 存放生命周期的屬性: 我們可以看到下面有一些簡寫的屬性例如:bc、c、bm等,這些都是生命周期鉤子,在這里需要注意的是,這些屬性的值應(yīng)該是一個數(shù)組,因為如果用戶使用了mixins或者extends這些屬性,那么同一個生命周期函數(shù)可能會包含多個,而這些生命周期函數(shù)都應(yīng)該被調(diào)用,所以他們的值應(yīng)當(dāng)是一個數(shù)組 例如:
export default {
  mixins:[{beforeCreate(){}}]
  beforeCreate(){}
}
//那么bc:[createBefore1,createBefore2]
  • 還有一些其他的屬性,我都放在了注釋當(dāng)中,因為看單詞意思就能理解,就不額外講解了,當(dāng)然后續(xù)這些屬性都將會用到,也許現(xiàn)在看這些屬性還是比較片面的。但是通過后面的講解,你將會逐漸了解這些屬性的作用。
function createComponentInstance(vnode, parent, suspense) {
  const type = vnode.type;
  const appContext = (parent ? parent.appContext : vnode.appContext) || {};
  const instance = {
    uid: uid++, //當(dāng)前實例的id
    vnode, //當(dāng)前實例對應(yīng)的vnode
    type, //當(dāng)前實例對應(yīng)的編譯后的.vue生成的對象
    parent, //當(dāng)前實例的父實例
    appContext, //app的上下文包含全局注入的插件,自定義指令等
    root: null, //當(dāng)前組件實例的根實例
    //響應(yīng)式觸發(fā)的更新next為null,
    //在更新的過程中父組件調(diào)用了子組件的
    //instance.update會賦值next為最新組件vnode
    next: null,
    subTree: null, //調(diào)用render函數(shù)后的Vnode(處理了透傳)
    effect: null, //實例的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,父實例有則讀取父實例的否則讀取app上的
    //父組件的provides后續(xù)會掛載到prototype上,重新賦值當(dāng)前真實
    //的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)前實例調(diào)用emit的函數(shù)
    emitted: null, //含有once修飾符的,執(zhí)行一次后放入這里不再執(zhí)行
    propsDefaults: {}, //默認(rèn)props
    //是否透傳attrs
    inheritAttrs: type.inheritAttrs,
    // state
    ctx: {}, //當(dāng)前實例的上下文也就是this
    data: {}, //data函數(shù)返回的值,被代理后才放入
    props: {}, //接受到的組件屬性
    attrs: {}, //接受到的標(biāo)簽屬性
    slots: {}, //組件傳遞的插槽內(nèi)容
    refs: {}, //存入的refs
    setupState: {}, //setup的返回值
    //expose attrs slots emit
    setupContext: null, //傳遞給setup的ctx(只有四個屬性)
    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
    //若組件實例是 <KeepAlive> 緩存樹的一部分,
    //當(dāng)組件從 DOM 中被移除時調(diào)用。deactivated
    da: null,
    //若組件實例是 <KeepAlive> 緩存樹的一部分,
    //當(dāng)組件被插入到 DOM 中時調(diào)用。activated
    a: null,
    //在一個響應(yīng)式依賴被組件觸發(fā)了重新渲染之后調(diào)用。
    //renderTriggered
    rtg: null,
    //在一個響應(yīng)式依賴被組件的渲染作用追蹤后調(diào)用。
    //renderTracked
    rtc: null,
    /**
     * 錯誤捕獲鉤子
     * 組件渲染
     * 事件處理器
     * 生命周期鉤子
     * setup() 函數(shù)
     * 偵聽器
     * 自定義指令鉤子
     * 過渡鉤子
     * 錯誤捕獲鉤子
     */
    ec: null, //errorHandler
    sp: null, //serverPrefetch
  };
  //創(chuàng)建實例的上下文
  instance.ctx = createDevRenderContext(instance);
  //當(dāng)前實例的根實例
  instance.root = parent ? parent.root : instance;
  instance.emit = emit.bind(null, instance);
  if (vnode.ce) {
    vnode.ce(instance);
  }
  return instance;
}

總結(jié)

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

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

相關(guān)文章

最新評論