欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3源碼分析組件掛載初始化props與slots

 更新時間:2022年10月21日 15:26:08   作者:豬豬愛前端  
這篇文章主要為大家介紹了Vue3源碼分析組件掛載初始化props與slots實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前情提要

  • 上文我們分析了掛載組件主要調(diào)用了三個函數(shù): createComponentInstance(創(chuàng)建組件實例)、setupComponent(初始化組件)、setupRenderEffect(更新副作用)。并且上一節(jié)中我們已經(jīng)詳細講解了組件實例上的所有屬性,還包括emit、provide等的實現(xiàn)。本文我們將繼續(xù)介紹組件掛載流程中的初始化組件。

本文主要內(nèi)容

  • 初始化props和slots的主要流程。
  • 如何將傳遞給組件的屬性分發(fā)給props和attrs(需要被透傳的屬性)。
  • 用戶自己實現(xiàn)了render函數(shù),如何對其進行標(biāo)準(zhǔn)化。
  • 標(biāo)準(zhǔn)的插槽需要滿足哪些條件。

初始化組件

(1).setupComponent

  • setupComponent: 這個函數(shù)主要用于初始化組件。內(nèi)部主要調(diào)用了initProps、initSlot、對于有狀態(tài)組件還需要調(diào)用setupStatefulComponent。
function setupComponent(instance) {
  //獲取vnode的props(真正傳遞的props)
  const { props, children } = instance.vnode;
  //判斷當(dāng)前是否是有狀態(tài)組件組件
  const isStateful = isStatefulComponent(instance);
  //通過傳遞的真實props和聲明的props 分離組件參數(shù)
  //組件參數(shù)放入props中 其余放入instance.attrs
  //處理了props的default情況等
  initProps(instance, props, isStateful);
  //初始化插槽
  initSlots(instance, children);
  //驗證名稱是否合法,components中的組件名稱是否
  //合法,代理instance.ctx,創(chuàng)建setup函數(shù)的ctx,調(diào)用setup函數(shù)
  //處理得到的結(jié)果
  const setupResult = isStateful ? 
        setupStatefulComponent(instance) : undefined;
  return setupResult;
}
  • isStatefulComponent: 這個主要用于判斷是否是有狀態(tài)組件、還記得Vue3源碼分析(4)中提到的ShapeFlag嗎?我們在createVNode中會判斷type的類型、然后設(shè)置shapeFlag來標(biāo)識當(dāng)前創(chuàng)建的虛擬節(jié)點類型。因此我們只需要獲取組件的vNode、而vNode中有shapeFlag然后判斷他的值,就知道他是不是有狀態(tài)組件了。
function isStatefulComponent(instance) {
  return instance.vnode.shapeFlag & 
         ShapeFlags.STATEFUL_COMPONENT;
}

(2).initProps

  • initProps: 在創(chuàng)建組件實例中,我們只對propsOptions做了處理、但是propsattrs目前都還是null、所以我們需要區(qū)分出來那些是props那些是attrs,同時有些propsOptions中設(shè)置了default屬性,那么我們還需要判斷是否傳遞了這個屬性,如果沒有傳遞那么應(yīng)該用default屬性中的值、又比如傳遞了 <Comp yes></Comp>并且聲明了props:{yes:Boolean},那么應(yīng)該將yes的值變?yōu)?code>true。而這些就是在初始化props的時候完成的。
function initProps(instance, rawProps, isStateful) {
  //定義需要放入的
  const props = {};
  const attrs = {};
  //attrs.__vInternal = 1
  shared.def(attrs, InternalObjectKey, 1);
  //創(chuàng)建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中解構(gòu)到options和needCastKeys(需要特殊處理的key)。options就是進行標(biāo)準(zhǔn)化后的組件定義的props
  • 遍歷真正傳遞給組件的props,拿到keyoptions中尋找,如果找到了,表示這個屬性是組件需要接受的props,進一步判斷是否是需要特殊處理的key如果不是就可以放入props中。
  • 如果是需要特殊處理的key,獲取他的值放入rawCastValues當(dāng)中。如果在options中沒有找到,就判斷一下emitsOptions中是否有,如果這里面也沒有那就可以放入attrs中,attrs就是需要透傳到subTree上的屬性。
  • 最后遍歷需要特殊處理的key調(diào)用resolvePropValueprops進行最后的處理。
function setFullProps(instance, rawProps, props, attrs) {
  //獲取通過mixins和extends合并的props
  const [options, needCastKeys] = instance.propsOptions;
  let hasAttrsChanged = false; //attrs是否發(fā)生改變
  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;
        }
      }
      //判斷當(dāng)前的key是否是用于emits的
      else if (!isEmitListener(instance.emitsOptions, key)) {
        //不是emit自定義事件的key也不是組件參數(shù)那么就是attrs
        if (!(key in attrs) || value !== attrs[key]) {
          attrs[key] = value;
          hasAttrsChanged = true;
        }
      }
    }
  }
  /**
   *
   * 這里涉及到四個屬性instance, rawProps, props, attrs
   * instance:是當(dāng)前組件的實例
   * rawProps:真正傳遞的props可能含有組件參數(shù)props,
   * 標(biāo)簽屬性attrs,自定義emit事件
   * props:代表聲明并且接受到的props
   * attrs:代表沒有聲明props也不屬于emits屬性的屬性
   * needCastKeys:代表需要特殊處理的屬性
   * 例如props:{msg:{default:"a"}}那么msg會被放入
   * needCastKeys中
   *
   */
  if (needCastKeys) {
    //獲取非響應(yīng)式的props
    const rawCurrentProps = reactivity.toRaw(props);
    const castValues = rawCastValues || {};
    for (let i = 0; i < needCastKeys.length; i++) {
      const key = needCastKeys[i]; //msg
      //對于有default的屬性進行重設(shè)
      //props:{msg:{default:"a"}}
      props[key] = resolvePropValue(
        options, //合并mixins和extends后的props(定義方)
        rawCurrentProps, //非響應(yīng)式的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的話表示需要使用默認(rèn)值,還需要進一步判斷,如果傳遞的不是函數(shù)但是聲明的是函數(shù),需要將value設(shè)置為這個函數(shù)的返回值。例如:props:{yes:Number,default:(props)=>{}}并且沒有向組件傳遞yes這個參數(shù),那么yes的值將會是default函數(shù)的返回值。
  • 對于propsOptions中定義的接受值類型是Boolean的,但是又沒有傳遞且沒有默認(rèn)值則設(shè)置這個值為false。
  • 當(dāng)然還有<Comp yes></Comp>并且聲明了是Boolean,則會設(shè)置為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;
      //如果需要接受的類型不是函數(shù),但是接受到了函數(shù)
      //看看實例的propsDefaults是否有當(dāng)前key的值
      //還是沒有則調(diào)用這個defaultValue函數(shù)取得值
      if (opt.type !== Function && shared.isFunction(defaultValue)) {
        const { propsDefaults } = instance;
        if (key in propsDefaults) {
          value = propsDefaults[key];
        } else {
          //包裹是為了在調(diào)用這個函數(shù)的時候
          //獲取當(dāng)前實例不會出錯
          setCurrentInstance(instance);
          value = propsDefaults[key] = defaultValue.call(null, props);
          unsetCurrentInstance();
        }
      }
      //設(shè)置為默認(rèn)值
      else {
        value = defaultValue;
      }
    }
    //需要接受的類型是Boolean
    if (opt[0]) {
      //沒有設(shè)置默認(rèn)值,也沒有傳遞這個值則為false
      if (isAbsent && !hasDefault) {
        value = false;
      }
      //<Comp yes></Comp>并且聲明了yes則設(shè)置為true
      else if (opt[1] && value === "") {
        value = true;
      }
    }
  }
  return value;
}

(3).initSlots

  • initSlots:還記得在Vue3源碼分析(4)中我們詳細講解了normalizeChildren,他主要用于標(biāo)準(zhǔn)化插槽,給vNodeshapeFlag加上ARRAY_CHILDRENTEXT_CHILDRENSLOTS_CHILDREN的標(biāo)識,但是并沒有添加到實例的slots屬性上。因為那個時候還沒有創(chuàng)建實例,所以我們只能在那時候打上標(biāo)記,在創(chuàng)建實例之后,也就是現(xiàn)在,在去初始化slots。對于SLOTS_CHILDREN、TEXT_CHILDREN、ARRAY_CHILDREN分別是在那種情況下添加到shapeFlag上的,如果你不了解可能會影響這一段代碼的閱讀,建議在看看第四小節(jié)。因為間隔較遠,所以理解起來很困難,這部分的文章主要是闡述整個Vue3的運行機制。我們后面的章節(jié)還會單獨講解slots的實現(xiàn)。
  • SLOTS_CHILDREN: 首先判斷children._是否存在,如果是通過Vue的編譯器得到的那么一定會有這個標(biāo)識,當(dāng)然,用戶自己書寫render函數(shù)也可以自己傳遞這個標(biāo)識符。但是大部分用戶是不會傳遞的,所以else分支中就是為了處理這種情況,而對于children._存在的,可以直接把children當(dāng)做實例的slots屬性。_標(biāo)識有三個值STABLE、DYNAMIC、FORWORD這個在第四小節(jié)也已經(jīng)講過了,就不在重復(fù)了。
  • TEXT_CHILDREN、ARRAY_CHILDREN: 因為children不是一個對象,而是數(shù)組或字符串或null,那么需要將其標(biāo)準(zhǔn)化為對象形式。調(diào)用normalizeVNodeSlots處理。
function initSlots(instance, children) {
  //判斷當(dāng)前實例的children是否是slots
  if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
    const type = children._; //獲取shapeSlots
    //有"_"標(biāo)識表示是通過compiler編譯得到的
    if (type) {
      //如果有SLOTS_CHILDREN標(biāo)識 表示children就是slots屬性
      instance.slots = reactivity.toRaw(children);
      //將_屬性變?yōu)椴豢擅杜e屬性
      shared.def(children, "_", type);
    } else {
      /**
       * render(){
       *   return h(Comp,null,{
       *     default:()=>h("div",null),
       *     header:()=>h("div",null)
       *   })
       * }
       * 沒有則表示用戶自己寫了render函數(shù)
       * 這個時候用戶可能不會添加"_"屬性
       * 所以需要對slots進行標(biāo)準(zhǔn)化
       */
      normalizeObjectSlots(children, (instance.slots = {}));
    }
  } else {
    instance.slots = {};
    //如果children為字符串或者null或數(shù)組情況
    if (children) {
      normalizeVNodeSlots(instance, children);
    }
  }
  //標(biāo)識slots為內(nèi)部屬性
  shared.def(instance.slots, InternalObjectKey, 1);
}
  • 我們先來看看到底要標(biāo)準(zhǔn)化成什么樣子,其實對于slots所有的標(biāo)準(zhǔn)化都是為了,將不標(biāo)準(zhǔn)的形式轉(zhuǎn)化為正常通過編譯得到的樣子。
  • 我們主要關(guān)注createBlock的第三個參數(shù)對象。通過觀察我們可以發(fā)現(xiàn)標(biāo)準(zhǔn)化的slots應(yīng)該滿足,
  • 一個具名插槽對應(yīng)一個創(chuàng)建好的VNode,我們這個例子只有default所以children對象中只有default
  • 并且必須由_withCtx包裹;(確保上下文,禁止block追蹤)
  • 參數(shù)必須是一個函數(shù),不能是數(shù)組;(提升性能)
  • 函數(shù)的返回值必須是一個數(shù)組。(標(biāo)準(zhǔn)化)
  • 如果你想自己書寫標(biāo)準(zhǔn)的插槽,你就應(yīng)當(dāng)滿足以上四個條件(我選擇模板編譯)。
<template>
  <Comp>
    我是插槽內(nèi)容
  </Comp>
</template>
//編譯后
function render(_ctx, _cache) {
  const _component_Comp = _resolveComponent("Comp", true)
  return (_openBlock(),
          _createBlock(_component_Comp, null, {
    default: _withCtx(() => [
      _createTextVNode(" 我是插槽內(nèi)容 ")
    ]),
    _: 1 /* STABLE */
  }))
}
  • normalizeObjectSlots: 改造成正常編譯后的樣子。因為沒有_標(biāo)識,所以不是通過編譯得到的,這將不能作為標(biāo)準(zhǔn)形式的slots,將其標(biāo)準(zhǔn)化。
  • 對于key以"_"開頭或key為$stable將不會進行標(biāo)準(zhǔn)化。
  • 判斷書寫的插槽模板是否是函數(shù),如果是則調(diào)用noramlizeSlot,如果不是警告用戶,應(yīng)該書寫函數(shù)形式,同樣標(biāo)準(zhǔn)化插槽的value然后包裝成函數(shù)在返回。
const normalizeObjectSlots = (rawSlots, slots, instance) => {
  const ctx = rawSlots._ctx;
  for (const key in rawSlots) {
    //_開頭或者$stable跳過
    //這將允許設(shè)置不進行標(biāo)準(zhǔn)化的插槽
    if (isInternalKey(key)) continue;
    //獲取slots的值
    const value = rawSlots[key];
    //如果value已經(jīng)是一個函數(shù)了,需要包裹withCtx執(zhí)行
    //進行標(biāo)準(zhǔn)化 都需要改成通過編譯的樣子
    if (shared.isFunction(value)) {
      //給instance.slots賦值
      slots[key] = normalizeSlot(key, value, ctx);
    }
    /**
     * 用戶不寫函數(shù),拋出警告,使用函數(shù)的性能將會更好
     * 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.`
      );
      //經(jīng)過normalizeSlotValue處理 返回的createVnode一定通過數(shù)組包裹
      const normalized = normalizeSlotValue(value);
      slots[key] = () => normalized;
    }
  }
};
  • normalizeSlot: key代表的是插槽名稱(具名插槽,默認(rèn)為default),rawSlot代表返回虛擬節(jié)點的函數(shù)(rawSlot=()=>createVNode()),所以這個函數(shù)本質(zhì)上是調(diào)用normalizeSlotValue對虛擬節(jié)點進行標(biāo)準(zhǔn)化,然后包裹_withCtx,最后返回經(jīng)過包裹的虛擬節(jié)點。接下來我們先看看withCtx執(zhí)行了什么。
const normalizeSlot = (key, rawSlot, ctx) => {
  //已經(jīng)經(jīng)過標(biāo)準(zhǔn)化的slot不需要在進行標(biāo)準(zhǔn)化
  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.`
      );
    }
    //標(biāo)準(zhǔn)化插槽的值 rawSlot=> default:()=>createVnode('div',null)
    return normalizeSlotValue(rawSlot(...args));
  }, ctx);
  //表示不是經(jīng)過compiler編譯的,是用戶自己寫的render函數(shù)
  normalized._c = false;
  return normalized;
};
  • withCtx: 將傳遞的fn包裹成renderFnWithContext在返回。
  • 在執(zhí)行fn的時候包裹一層currentRenderInstance,確保當(dāng)前的實例不出錯。
  • renderFnWithContext有以下三個屬性:
  • _n:如果有這個屬性代表當(dāng)前函數(shù)已經(jīng)被包裹過了,不應(yīng)該被重復(fù)包裹。
  • _c: 標(biāo)識的是當(dāng)前的插槽是通過編譯得到的,還是用戶自己寫的。
  • _d: 表示執(zhí)行fn的時候是否需要禁止塊跟蹤,true代表禁止塊跟蹤,false代表允許塊跟蹤。
function withCtx(
  fn,
  ctx = getCurrentRenderingInstance(),
  isNonScopedSlot
) {
  if (!ctx) return fn;
  if (fn._n) {
    return fn;
  }
  //設(shè)置currentRenderingInstance,通過閉包確保調(diào)用fn的時候
  //currentRenderingInstance實例為當(dāng)前實例
  /**
   * 如果用戶調(diào)用模板表達式內(nèi)的插槽
   *  <Button>
   *    <template>
   *      <slot></slot>
   *    </template>
   *  </Button>
   * 可能會擾亂塊跟蹤,因此默認(rèn)情況下,禁止塊跟蹤,當(dāng)
   * 調(diào)用已經(jīng)編譯的插槽時強制跳出(由.d標(biāo)志指示)。
   * 如果渲染已編譯的slot則無需執(zhí)行此操作、因此
   * 我們在renderSlot中調(diào)用renderFnWithContext
   * 時,.d設(shè)置為false
   */
  const renderFnWithContext = (...args) => {
    //禁止塊追蹤,將isBlockTreeEnabled設(shè)置為0將會停止追蹤
    if (renderFnWithContext._d) {
      setBlockTracking(-1);
    }
    const prevInstance = setCurrentRenderingInstance(ctx);
    const res = fn(...args);
    setCurrentRenderingInstance(prevInstance);
    //開啟塊追蹤
    if (renderFnWithContext._d) {
      setBlockTracking(1);
    }
    return res;
  };
  //如果已經(jīng)是renderFnWithContext則不需要在包裝了
  renderFnWithContext._n = true; //_n表示已經(jīng)經(jīng)過renderFnWithContext包裝
  renderFnWithContext._c = true; //表示經(jīng)過compiler編譯得到
  //true代表禁止塊追蹤,false代表開啟塊追蹤
  renderFnWithContext._d = true;
  return renderFnWithContext;
}
  • normalizeSlotValue: 目前value傳遞的是單個VNode或者是數(shù)組類型的VNode,我們還需要對返回的所有VNode進行標(biāo)準(zhǔn)化。這里主要是為了處理,比如default:()=>"asd",如果是字符串,他顯然可以這樣寫,但是我們需要將字符串變成patch階段能夠處理的VNode
function normalizeSlotValue(value){
  if(shared.isArray(value)){
    return value.map(normalizeVNode)
  }
  return [normalizeVNode(value)] 
}
  • normalizeVNode: 標(biāo)準(zhǔn)化虛擬節(jié)點。
  • 當(dāng)前虛擬節(jié)點是null、boolean,這樣的值不應(yīng)該顯示在頁面當(dāng)中,創(chuàng)建注釋節(jié)點。
  • 當(dāng)前虛擬節(jié)點是一個數(shù)組,需要由Fragment包裹。例如下面的寫法。
//自己寫render函數(shù)
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>([
       createVNode('div',null),
       createVNode('div',null)
       ])
    })
  }
}
//如果是正常編譯獲得的那么應(yīng)該是
  • 如果是object,判斷當(dāng)前節(jié)點是否掛載過,掛載過需要克隆節(jié)點再返回。例如下面這種情況:
export default{
  render(){
    return createVNode(Comp,null,{
      default:()=>createTextVNode('123')
    })
  }
}
  • 如果是字符串或者number,創(chuàng)建文本節(jié)點即可。例如下面這種情況:
//自己寫render函數(shù)
export default {
  render(){
    return createVNode(Comp,null,{
      default:()=>123
    })
  }
}
function normalizeVNode(child) {
  if (child == null || typeof child === "boolean") {
    //沒有child或者沒有實質(zhì)性內(nèi)容創(chuàng)建注釋節(jié)點
    return createVNode(Comment);
  } else if (shared.isArray(child)) {
    //用戶直接寫了一個數(shù)組,需要包裹一層Fragment
    return createVNode(Fragment, null, child.slice());
  }
  //如果這個節(jié)點已經(jīng)掛載過了克隆這個節(jié)點(復(fù)用節(jié)點)
  else if (typeof child === "object") {
    return cloneIfMounted(child);
  }
  //string 或者 number
  else {
    return createVNode(Text, null, String(child));
  }
}
  • 到此為止我們就完成了對于對象形式的插槽標(biāo)準(zhǔn)化,并放到了實例的slots屬性上。 現(xiàn)在你可以通過訪問slots.default訪問到經(jīng)過標(biāo)準(zhǔn)化后的虛擬節(jié)點了。而我們實際在項目中使用的是<slot name="default"></slot>,這個又是怎么渲染到頁面上的呢?大膽猜測一下就是根據(jù)name屬性獲取到key然后到instance.slots中去找到這個虛擬節(jié)點最后掛載到頁面就可以了。我們會在講解slots的實現(xiàn)章節(jié)詳細解釋,這里就不過多講解了。
render(){
  return createVNode(Comp,null,{
    default:createVNode('div')
  })
}
//經(jīng)過標(biāo)準(zhǔn)化后,相當(dāng)于
render(){
  return createVNode(Comp,null,{
    default:withCtx(()=>[createVNode('div')])
  })
}
//其他的情況都差不多,都是為了標(biāo)準(zhǔn)化為
//滿足上面四個條件的樣子
  • 下面我們講解另一個分支,如果用戶用數(shù)組或字符串或數(shù)字作為children參數(shù)呢?createVNode(Comp,null,[])就像這樣。又或者createVNode(Comp,null,123)這樣。這就是標(biāo)識為ARRAY_CHILDRENTEXT_CHILDREN的情況了,顯然調(diào)用了normalizeVNodeSlots進行處理。
  • normalizeVNodeSlots:這個情況我們可以把傳遞的第三個參數(shù)看成是調(diào)用對象形式的default函數(shù)的返回值,那么我們只需要標(biāo)準(zhǔn)化第三個參數(shù)然后包裝成一個函數(shù),賦值給slots.default就可以啦。
const normalizeVNodeSlots = (instance, children) => {
  const normalized = normalizeSlotValue(children);
  instance.slots.default = () => normalized;
};

額外內(nèi)容

  • normalizeVNode函數(shù)中,如果傳遞的child是一個對象,那么調(diào)用了cloneIfMounted,這個函數(shù)是干什么的呢?如果el有值,表示已經(jīng)有真實的DOM了,那么就一定調(diào)用了render函數(shù),也一定掛載過元素了。我們看看他是如何克隆節(jié)點的呢?
//掛載過的vnode有el屬性
function cloneIfMounted(child) {
  return child.el === null || child.memo ? 
         child : cloneVNode(child);
}
  • cloneVNode: 用于淺克隆一個VNode。還可以提供額外的props合并之前VNode身上的屬性。
  • 如果提供了extraProps,調(diào)用mergeProps合并之前的props和新的props。對key為class、style的屬性做了特殊處理。并且后面的props可以覆蓋前面的props
  • 當(dāng)keyclass的時候,之前的class已經(jīng)經(jīng)過標(biāo)準(zhǔn)化了一定是一個字符串,我們需要將新的class與之前的class合并為一個字符串。
  • 當(dāng)keystyle的時候,合并新舊的style對象。
  • 其余情況,讓新的覆蓋舊的。
function mergeProps(...args) {
  const ret = {};
  for (let i = 0; i < args.length; i++) {
    const toMerge = args[i];
    for (const key in toMerge) {
      //結(jié)合class
      if (key === "class") {
        if (ret.class !== toMerge.class) {
          ret.class = shared.normalizeClass([ret.class, toMerge.class]);
        }
      }
      //結(jié)合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參數(shù),表示需要合并ref,那么需要讀取mergeProps中的ref屬性進行合并,之前的ref可能是數(shù)組(使用了v-for加ref),將最新的ref添加到數(shù)組的后面,不是數(shù)組則轉(zhuǎn)化為數(shù)組在合并他們兩個ref到這個數(shù)組中。
  • 對于靜態(tài)節(jié)點,需要深度克隆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;
}

總結(jié)

  • 本文我們主要介紹了如何對生成的組件實例的props和slots屬性進行初始化。
  • 在初始化props中,根據(jù)定義組件的props和接受到的props放到instance.props中,對于定義了但是沒有傳遞,又有默認(rèn)值的我們需要使用默認(rèn)值。當(dāng)然我們還需要設(shè)置透傳屬性attrs的值,如果傳遞了,但是沒有在props、emits中定義,那么會認(rèn)為是透傳屬性,需要將其放入到instance.attrs中。
  • 然后我們詳細講解了slots的初始化。這一部分主要是對用戶自己使用render函數(shù)來渲染的模板,進行標(biāo)準(zhǔn)化保證后續(xù)的執(zhí)行不會出錯。
  • 最后我們在額外內(nèi)容中介紹了cloneVNodeapi實現(xiàn)。
  • 下文中我們將會繼續(xù)講解,對于其他組件定義的屬性的初始化。也就是setupStatefulComponent函數(shù),這里將會對watch、data、computed等屬性進行處理,調(diào)用setup函數(shù)、beforeCreat,created鉤子等。

以上就是Vue3源碼分析組件掛載初始化props與slots的詳細內(nèi)容,更多關(guān)于Vue3組件掛載初始化的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別

    Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別

    這篇文章主要介紹了Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • Vue 子組件更新props中的屬性值問題

    Vue 子組件更新props中的屬性值問題

    這篇文章主要介紹了Vue 子組件更新props中的屬性值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • vue中的@click.native 原生點擊事件

    vue中的@click.native 原生點擊事件

    這篇文章主要介紹了vue中的@click.native 原生點擊事件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue?websocket封裝實現(xiàn)方法詳解

    Vue?websocket封裝實現(xiàn)方法詳解

    項目中需要使用websocke,這個是我自己修修改改好多次之后用得最順手的一版,分享一下。這個封裝需要搭配vuex使用,因為收到的數(shù)據(jù)都保存在vuex中了,真的絕絕子好用,解決了我一大堆問題
    2022-09-09
  • Vue 3.x+axios跨域方案的踩坑指南

    Vue 3.x+axios跨域方案的踩坑指南

    這篇文章主要給大家介紹了關(guān)于Vue 3.x+axios跨域方案的踩坑指南,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Vue 3.x具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • vite+vue3搭建的工程實現(xiàn)批量導(dǎo)入store的module

    vite+vue3搭建的工程實現(xiàn)批量導(dǎo)入store的module

    這篇文章主要介紹了vite+vue3搭建的工程實現(xiàn)批量導(dǎo)入store的module方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • vue3+ts封裝axios實例以及解決跨域問題

    vue3+ts封裝axios實例以及解決跨域問題

    在前端開發(fā)中,使用axios進行數(shù)據(jù)請求是常見的做法,封裝axios可以統(tǒng)一請求頭處理、方便接口管理、配置多攔截器等,提高代碼的可維護性和重用性,本文詳細記錄了axios的封裝過程,包括安裝、配置跨域處理、接口管理文件的創(chuàng)建等
    2024-09-09
  • 利用vue和element-ui設(shè)置表格內(nèi)容分頁的實例

    利用vue和element-ui設(shè)置表格內(nèi)容分頁的實例

    下面小編就為大家分享一篇利用vue和element-ui設(shè)置表格內(nèi)容分頁的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • 關(guān)于vue-color-顏色選擇器插件

    關(guān)于vue-color-顏色選擇器插件

    這篇文章主要介紹了關(guān)于vue-color-顏色選擇器插件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • Vue實現(xiàn)全局的toast組件方式

    Vue實現(xiàn)全局的toast組件方式

    這篇文章主要介紹了Vue實現(xiàn)全局的toast組件方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03

最新評論