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

源碼淺析Vue3中的組件掛載

 更新時(shí)間:2023年09月07日 16:03:09   作者:沽汣  
這篇文章主要帶大家從源碼分析一下Vue3中的組件掛載的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起了解一下

前言

前面我們一起探討了 Vue3 的 響應(yīng)式原理編譯過程,render函數(shù)我們已經(jīng)拿到了,那具體到底要怎么用呢?這一節(jié),我們就開啟一個(gè)新篇章——組件的掛載。

組件掛載/更新函數(shù)——setupRenderEffect

組件掛載和更新的核心函數(shù)都是setupRenderEffect,這里我們就從setupRenderEffect函數(shù)作為切入點(diǎn):

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
      const componentUpdateFn = () => {
        if (!instance.isMounted) {
          // 組件尚未掛載,執(zhí)行掛載操作
          ...
        } else {
          // 組件已經(jīng)掛載,執(zhí)行更新操作
          ...
      };
      // 初始化響應(yīng)式副作用函數(shù)
      const effect = instance.effect = new ReactiveEffect(
        componentUpdateFn,
        () => queueJob(update),
        instance.scope
      );
      const update = instance.update = () => effect.run();
      update.id = instance.uid;
      ...
      // 執(zhí)行副作用函數(shù)
      update();
    };

我們可以看到在 setupRenderEffect 函數(shù)中:

  • 首先,定義了一個(gè) 組件掛載/更新函數(shù) componentUpdateFn,該函數(shù)會(huì)根據(jù)組件實(shí)例的是否已經(jīng)掛載來進(jìn)行不同的操作。
  • 然后,將 掛載/更新函數(shù) componentUpdateFn 包裝為一個(gè) effect副作用函數(shù)。
  • 最后,執(zhí)行副作用函數(shù),完成掛載或更新操作。

可以看出,組件更新的核心就在于 componentUpdateFn 函數(shù),接下來我們深入來看一下這個(gè)函數(shù)內(nèi)部都執(zhí)行了哪些操作。

componentUpdateFn

我們首先來看實(shí)例尚未掛載的情況下,componentUpdateFn函數(shù)是如何處理掛載的:

const componentUpdateFn = () => {
  // 組件尚未掛載,執(zhí)行掛載操作
  if (!instance.isMounted) {
    let vnodeHook;
    const { el, props } = initialVNode;
    const { bm, m, parent } = instance;
    ...
    // 將實(shí)例上的allowRecurse屬性(允許遞歸)設(shè)置為false
    toggleRecurse(instance, false);
    // 如果存在onBeforeMount生命周期函數(shù)
    if (bm) {
      // 執(zhí)行onBeforeMount中的函數(shù)
      invokeArrayFns(bm);
    }
    ...
    // 將實(shí)例上的allowRecurse屬性(允許遞歸)設(shè)置為true
    toggleRecurse(instance, true);
    if (el && hydrateNode) {
      ...
    } else {
     // 生成子樹的vnode
      const subTree = (instance.subTree = renderComponentRoot(instance));
      // 掛載子樹vnode到容器中
      patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
      initialVNode.el = subTree.el;
    }
    ...
    // 將實(shí)例上的isMounted屬性設(shè)置為true
    instance.isMounted = true;
    initialVNode = container = anchor = null;
  } else {
    // 組件已經(jīng)掛載過,執(zhí)行更新操作
    ...
};

componentUpdateFn在處理組件掛載時(shí)主要做的事情就是:

  • 首先,判斷組件是否存在beforeMount生命周期函數(shù),如果存在,則執(zhí)行內(nèi)部定義的函數(shù)。
  • 然后,根據(jù)實(shí)例instance生成子樹vnode。
  • 之后,通過patch函數(shù),將子樹vnode掛載到容器。(因?yàn)槟壳笆菕燧d階段,所以patch函數(shù)第一個(gè)參數(shù)默認(rèn)設(shè)定為了null
  • 最后,將對(duì)應(yīng)的屬性值isMounted進(jìn)行相關(guān)配置,將變量指針置空。

接下來,我們進(jìn)入renderComponentRoot函數(shù),看一看生成子樹vnode的整個(gè)過程是怎樣的。

生成vnode的函數(shù)——renderComponentRoot

function renderComponentRoot( instance: ComponentInternalInstance ): VNode {
  ...
  let result
  ...
      const proxyToUse = withProxy || proxy
      // 取出render函數(shù),并執(zhí)行
      result = normalizeVNode(
        render!.call(
          proxyToUse,
          proxyToUse!,
          renderCache,
          props,
          setupState,
          data,
          ctx
        )
      )
  ...
  return result
}

上面我們抽離出該函數(shù)的核心,可以看到,renderComponentRoot函數(shù)的關(guān)鍵邏輯就是執(zhí)行了render函數(shù)。

前面章節(jié)中,我們已經(jīng)介紹了render函數(shù)生成的過程,我們還用之前的例子:

模板template:

<div>
    <span> {{x}} </span>
    <div>123</div>
</div>

經(jīng)過編譯后,生成的render函數(shù)是這個(gè)樣子的:

function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    return (_openBlock(), _createElementBlock("div", null, [
      _createElementVNode("span", null, _toDisplayString(x), 1 /* TEXT */),
      _hoisted_1
    ]))
  }
}

最終,我們通過執(zhí)行上面的render函數(shù),得到節(jié)點(diǎn)的虛擬DOM也就是vnode

然后,我們將這樣的一個(gè)數(shù)據(jù)結(jié)構(gòu)(vnode)傳入normalizeVNode函數(shù)中做進(jìn)一步的處理,我們看一下normalizeVNode函數(shù)又做了什么操作:

function normalizeVNode(child) {
    // 如果節(jié)點(diǎn)vnode為空,則創(chuàng)建為注釋節(jié)點(diǎn)
    if (child == null || typeof child === "boolean") {
      return createVNode(Comment);
    } else if (isArray(child)) {
      // 如果節(jié)點(diǎn)為數(shù)組,則在外層包裹一層根節(jié)點(diǎn)fragment
      return createVNode(
        Fragment,
        null,
        child.slice()
      );
    } else if (typeof child === "object") {
      // 如果是對(duì)象形式
      return cloneIfMounted(child);
    } else {
      // 其他情況,比如創(chuàng)建文本類型的節(jié)點(diǎn)
      return createVNode(Text, null, String(child));
    }
}
function cloneIfMounted(child) {
    // 如果節(jié)點(diǎn)已經(jīng)掛載,則直接返回對(duì)應(yīng)的vnode,否則克隆一份返回
    return child.el === null && child.patchFlag !== -1 /* HOISTED */ || child.memo ? child : cloneVNode(child);
  }

在這個(gè)函數(shù)中會(huì)對(duì)傳入的參數(shù)進(jìn)行分情況討論:

  • 如果參數(shù)vnode,則創(chuàng)建為注釋節(jié)點(diǎn)。
  • 如果參數(shù)vnode數(shù)組,則在外層包裹一層根節(jié)點(diǎn)Fragment,再執(zhí)行創(chuàng)建vnode的函數(shù)。
  • 如果參數(shù)vnode對(duì)象形式,則直接返回或克隆該節(jié)點(diǎn)vnode。
  • 其他情況,主要是像文本類型節(jié)點(diǎn)的處理。

我們傳入的 child參數(shù) 是一個(gè)對(duì)象形式,所以會(huì)最終執(zhí)行的是cloneIfMounted函數(shù),而這個(gè)函數(shù)中,會(huì)去判斷 vnode節(jié)點(diǎn) 是否已經(jīng)被掛載過,如果已經(jīng)執(zhí)行過掛載操作,那么其 vnodeel屬性上就會(huì)被賦值,該函數(shù)就直接將原vnode節(jié)點(diǎn)返回,否則,執(zhí)行拷貝操作再返回。

這里,因?yàn)榈慕M件在該階段還未掛載,所以normalizeVNode函數(shù)最終的返回結(jié)果也是直接將上面render函數(shù)生成的vnode直接返回,而我們最終renderComponentRoot函數(shù)的返回值同樣也是執(zhí)行render函數(shù)得到的vnode。

至此,我們大概理清楚了生成子樹vnode的函數(shù)renderComponentRoot的邏輯,它的主要工作就是通過執(zhí)行模板編譯后生成的render函數(shù),再進(jìn)行相應(yīng)的處理,得到最終的vnode。

掛載/更新函數(shù)patch

接下來我們開啟下一個(gè)環(huán)節(jié),也是vue中極其重要的一個(gè)函數(shù)——patch函數(shù)。

patch直譯過來就是“補(bǔ)丁”的意思,可以理解為在vue中,組件的掛載和更新都是通過打“補(bǔ)丁”的方式來進(jìn)行的。當(dāng)然,打“補(bǔ)丁”前要先比對(duì)一下,看看兩個(gè)節(jié)點(diǎn)到底是哪些信息不一樣了,然后再進(jìn)行定點(diǎn)的更新。

在進(jìn)入patch函數(shù)之前先說明一下patch函數(shù)的幾個(gè)關(guān)鍵參數(shù):

  • n1: 舊vnode節(jié)點(diǎn)
  • n2: 新vnode節(jié)點(diǎn)
  • container: 掛載的容器
  • anchor: 掛載的參考元素
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = isHmrUpdating ? false : !!n2.dynamicChildren) => {
      // 如果新舊vnode節(jié)點(diǎn)相同,則無須patch
      if (n1 === n2) {
        return;
      }
      // 如果新舊vnode節(jié)點(diǎn),type類型不同,則直接卸載舊節(jié)點(diǎn)
      // 這里isSameVNodeType會(huì)判斷規(guī)則為n1.type === n2.type && n1.key === n2.key
      if (n1 && !isSameVNodeType(n1, n2)) {
        anchor = getNextHostNode(n1);
        unmount(n1, parentComponent, parentSuspense, true);
        n1 = null;
      }
      ...
      const { type, ref: ref2, shapeFlag } = n2;
      // 根據(jù)新節(jié)點(diǎn)的類型,采用不同的函數(shù)進(jìn)行處理
      switch (type) {
        // 處理文本
        case Text:
          processText(n1, n2, container, anchor);
          break;
        // 處理注釋
        case Comment:
          processCommentNode(n1, n2, container, anchor);
          break;
        // 處理靜態(tài)節(jié)點(diǎn)
        case Static:
          if (n1 == null) {
            mountStaticNode(n2, container, anchor, isSVG);
          } else if (true) {
            patchStaticNode(n1, n2, container, isSVG);
          }
          break;
        // 處理Fragment
        case Fragment:
          // Fragment
          ...
          break;
        default:
          if (shapeFlag & 1 /* ELEMENT */) {
            // element類型
            processElement(
              n1,
              n2,
              container,
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              slotScopeIds,
              optimized
            );
          } else if (shapeFlag & 6 /* COMPONENT */) {
            // 組件
            ...
          } else if (shapeFlag & 64 /* TELEPORT */) {
            // teleport 
            ...
          } else if (shapeFlag & 128 /* SUSPENSE */) {
            // suspense
            ...
          } else if (true) {
            warn2("Invalid VNode type:", type, `(${typeof type})`);
          }
      }
      ...
    };

patch函數(shù)整體的處理邏輯就是:

  • 比對(duì)新舊節(jié)點(diǎn),如果新舊節(jié)點(diǎn)相同,則無須處理。
  • 如果新舊節(jié)點(diǎn)的類型不同,則直接將舊節(jié)點(diǎn)卸載(我們這一節(jié)主要研究掛載階段,所以舊節(jié)點(diǎn)為null,可以先不關(guān)注這一點(diǎn))。
  • 根據(jù)新節(jié)點(diǎn)的類型,再分情況進(jìn)行處理。

這里,我們就用處理element類型來舉例,看一下processElement函數(shù),其他情況同理。

processElement函數(shù)

const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
      isSVG = isSVG || n2.type === "svg";
      if (n1 == null) {
       // 如果存在舊節(jié)點(diǎn)
        mountElement(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        );
      } else {
        // 舊節(jié)點(diǎn)不存在
        patchElement(
          n1,
          n2,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        );
      }
    };

processElement函數(shù),會(huì)根據(jù)舊節(jié)點(diǎn)是否存在進(jìn)行分情況討論,這里我們主要看掛載階段的函數(shù)——mountElement。

const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
      let el;
      let vnodeHook;
      const { type, props, shapeFlag, transition, dirs } = vnode;
      // 創(chuàng)建真實(shí)DOM結(jié)構(gòu),并將其保存在在vnode的el屬性上
      el = vnode.el = hostCreateElement(
        vnode.type,
        isSVG,
        props && props.is,
        props
      );
      // 處理文本節(jié)點(diǎn)
      if (shapeFlag & 8 /* TEXT_CHILDREN */) {
        hostSetElementText(el, vnode.children);
      } else if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
        // 如果節(jié)點(diǎn)類型是數(shù)組,則遞歸的對(duì)子節(jié)點(diǎn)進(jìn)行處理
        mountChildren(
          vnode.children,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type !== "foreignObject",
          slotScopeIds,
          optimized
        );
      }
      // 處理vnode上的指令相關(guān)內(nèi)容,并執(zhí)行指令的生命周期鉤子函數(shù)
      if (dirs) {
        invokeDirectiveHook(vnode, null, parentComponent, "created");
      }
      setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent);
      // 處理props相關(guān)內(nèi)容
      if (props) {
        for (const key in props) {
          if (key !== "value" && !isReservedProp(key)) {
            hostPatchProp(
              el,
              key,
              null,
              props[key],
              isSVG,
              vnode.children,
              parentComponent,
              parentSuspense,
              unmountChildren
            );
          }
        }
        if ("value" in props) {
          hostPatchProp(el, "value", null, props.value);
        }
        if (vnodeHook = props.onVnodeBeforeMount) {
          invokeVNodeHook(vnodeHook, parentComponent, vnode);
        }
      }
      ...
      // 處理vnode上的指令相關(guān)內(nèi)容,并執(zhí)行指令的生命周期鉤子函數(shù)
      if (dirs) {
        invokeDirectiveHook(vnode, null, parentComponent, "beforeMount");
      }
      ...
      // 將dom掛載到container
      hostInsert(el, container, anchor);
      ...
    };

mountElement函數(shù)中,我們終于創(chuàng)建出了期待已久的真實(shí)DOM結(jié)構(gòu)。該函數(shù)的邏輯為:

根據(jù)vnode創(chuàng)建出真實(shí)DOM結(jié)構(gòu),并保存在el屬性上。

根據(jù)子節(jié)點(diǎn)類型來進(jìn)行不同的操作:

  • 文本類型,直接生成文本節(jié)點(diǎn)
  • 數(shù)組類型,則遞歸的處理子節(jié)點(diǎn)

對(duì)vnode的指令以及props內(nèi)容進(jìn)行處理。

最后將生成的DOM掛載到容器container上,也就最終呈現(xiàn)在頁面上了。

這里可能有的朋友就是想看一看document.createElement這種API到底在哪里,那就提一下hostCreateElement函數(shù)

hostCreateElement函數(shù)在源碼中是通過解構(gòu)賦值并重命名得來的,它原來的名字叫createElement,改回本名瞬間就直觀了很多~

createElement: (tag, isSVG, is, props) => {
  const el = isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag, is ? { is } : void 0);
  if (tag === "select" && props && props.multiple != null) {
    ;
    el.setAttribute("multiple", props.multiple);
  }
  return el;
}
// hostInsert指向的函數(shù)就是insert
insert: (child, parent, anchor) => {
  parent.insertBefore(child, anchor || null);
},

兩個(gè)工具函數(shù)的邏輯也很好理解,就不再多說了吧。總之,終于是看到了document.createElement就是舒服了^_^

最后

這一節(jié),我們深入研究了Vue3中,組件的掛載邏輯,整個(gè)的流程雖然過程繁瑣,但要做的事比較清晰,總結(jié)下來就是:

  • 判斷當(dāng)前vnode是否已經(jīng)進(jìn)行過掛載操作,來決定是進(jìn)行掛載流程還是更新流程。
  • 進(jìn)入掛載流程。
  • 執(zhí)行模板編譯階段生成的render函數(shù),得到虛擬dom(vnode)。
  • 通過patch函數(shù),對(duì)vnode進(jìn)行分類處理,同時(shí)在這個(gè)階段創(chuàng)建出真實(shí)的DOM結(jié)構(gòu)。
  • 將創(chuàng)建的DOM掛載到容器container中,完成最終呈現(xiàn)。

到此這篇關(guān)于源碼淺析Vue3中的組件掛載的文章就介紹到這了,更多相關(guān)Vue3組件掛載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue實(shí)現(xiàn)提示保存后退出的方法

    vue實(shí)現(xiàn)提示保存后退出的方法

    下面小編就為大家分享一篇vue實(shí)現(xiàn)提示保存后退出的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • 一文解決vue2 element el-table自適應(yīng)高度問題

    一文解決vue2 element el-table自適應(yīng)高度問題

    在寫公司后臺(tái)項(xiàng)目的時(shí)候遇到一個(gè)需求,要求表格頁面不能有滾動(dòng)條,所以必須封裝一個(gè)公共方法來實(shí)現(xiàn)表格自適應(yīng)高度,本問小編給大家介紹了如何解決vue2 element el-table自適應(yīng)高度問題,需要的朋友可以參考下
    2023-11-11
  • vue.config.js中devServer.proxy配置說明及配置正確不生效問題解決

    vue.config.js中devServer.proxy配置說明及配置正確不生效問題解決

    Vue項(xiàng)目devServer.proxy代理配置詳解的是一個(gè)非常常見的需求,下面這篇文章主要給大家介紹了關(guān)于vue.config.js中devServer.proxy配置說明及配置正確不生效問題解決的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • Vue實(shí)現(xiàn)動(dòng)態(tài)顯示表單項(xiàng)填寫進(jìn)度功能

    Vue實(shí)現(xiàn)動(dòng)態(tài)顯示表單項(xiàng)填寫進(jìn)度功能

    這篇文章主要介紹了Vue實(shí)現(xiàn)動(dòng)態(tài)顯示表單項(xiàng)填寫進(jìn)度功能,此功能可以幫助用戶了解表單填寫的進(jìn)度和當(dāng)前狀態(tài),提高用戶體驗(yàn),通常實(shí)現(xiàn)的方式是在表單中添加進(jìn)度條,根據(jù)用戶填寫狀態(tài)動(dòng)態(tài)更新進(jìn)度條,感興趣的同學(xué)可以參考下文
    2023-05-05
  • 集成vue到j(luò)query/bootstrap項(xiàng)目的方法

    集成vue到j(luò)query/bootstrap項(xiàng)目的方法

    下面小編就為大家分享一篇集成vue到j(luò)query/bootstrap項(xiàng)目的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • vue生命周期和react生命周期對(duì)比【推薦】

    vue生命周期和react生命周期對(duì)比【推薦】

    本文通過實(shí)例代碼給大家介紹了vue生命周期和react生命周期對(duì)比 ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-09-09
  • vue fetch中的.then()的正確使用方法

    vue fetch中的.then()的正確使用方法

    這篇文章主要介紹了vue fetch中的.then()的正確使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • Vue中對(duì)iframe實(shí)現(xiàn)keep alive無刷新的方法

    Vue中對(duì)iframe實(shí)現(xiàn)keep alive無刷新的方法

    這篇文章主要介紹了Vue中對(duì)iframe實(shí)現(xiàn)keep alive無刷新的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 解決webpack+Vue引入iView找不到字體文件的問題

    解決webpack+Vue引入iView找不到字體文件的問題

    今天小編就為大家分享一篇解決webpack+Vue引入iView找不到字體文件的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • 解決element-ui table設(shè)置列fixed時(shí)X軸滾動(dòng)條無法拖動(dòng)問題

    解決element-ui table設(shè)置列fixed時(shí)X軸滾動(dòng)條無法拖動(dòng)問題

    這篇文章主要介紹了解決element-ui table設(shè)置列fixed時(shí)X軸滾動(dòng)條無法拖動(dòng)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10

最新評(píng)論