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

源碼淺析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)動(dòng)態(tài)路由

    vue一步到位的實(shí)現(xiàn)動(dòng)態(tài)路由

    這篇文章主要介紹了vue一步到位的實(shí)現(xiàn)動(dòng)態(tài)路由,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • vue的toast彈窗組件實(shí)例詳解

    vue的toast彈窗組件實(shí)例詳解

    本文通過實(shí)例給大家講解了vue的toast彈窗組件功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2018-05-05
  • vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法

    vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法

    下面小編就為大家分享一篇vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • Hexo已經(jīng)看膩了,來手把手教你使用VuePress搭建個(gè)人博客

    Hexo已經(jīng)看膩了,來手把手教你使用VuePress搭建個(gè)人博客

    vuepress是尤大大4月12日發(fā)布的一個(gè)全新的基于vue的靜態(tài)網(wǎng)站生成器,實(shí)際上就是一個(gè)vue的spa應(yīng)用,內(nèi)置webpack,可以用來寫文檔。這篇文章給大家介紹了VuePress搭建個(gè)人博客的過程,感興趣的朋友一起看看吧
    2018-04-04
  • js 實(shí)現(xiàn)拖拽排序詳情

    js 實(shí)現(xiàn)拖拽排序詳情

    這篇文章主要介紹了js 實(shí)現(xiàn)拖拽排序,拖拽排序?qū)τ谛』锇閭儊碚f應(yīng)該不陌生,平時(shí)工作的時(shí)候,可能會(huì)選擇使用類似Sortable.js這樣的開源庫來實(shí)現(xiàn)需求。但在完成需求后,大家有沒有沒想過拖拽排序是如何實(shí)現(xiàn)的呢?感興趣得話一起來看看下面文章得小心內(nèi)容吧
    2021-11-11
  • 詳解Vue單元測(cè)試case寫法

    詳解Vue單元測(cè)試case寫法

    這篇文章主要介紹了詳解Vue單元測(cè)試case寫法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • Vue3項(xiàng)目頁面實(shí)現(xiàn)echarts圖表漸變色的動(dòng)態(tài)配置的實(shí)現(xiàn)步驟

    Vue3項(xiàng)目頁面實(shí)現(xiàn)echarts圖表漸變色的動(dòng)態(tài)配置的實(shí)現(xiàn)步驟

    在開發(fā)可配置業(yè)務(wù)平臺(tái)時(shí),需要實(shí)現(xiàn)讓用戶對(duì)項(xiàng)目?jī)?nèi)echarts圖表的動(dòng)態(tài)配置,讓用戶脫離代碼也能實(shí)現(xiàn)簡(jiǎn)單的圖表樣式配置,顏色作為圖表樣式的重要組成部分,其配置方式是項(xiàng)目要解決的重點(diǎn)問題,所以本文介紹了Vue3項(xiàng)目頁面實(shí)現(xiàn)echarts圖表漸變色的動(dòng)態(tài)配置
    2024-10-10
  • Vue項(xiàng)目識(shí)別不到@別名問題及解決

    Vue項(xiàng)目識(shí)別不到@別名問題及解決

    這篇文章主要介紹了Vue項(xiàng)目識(shí)別不到@別名問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue調(diào)用谷歌授權(quán)登錄獲取用戶通訊錄的實(shí)現(xiàn)示例

    vue調(diào)用谷歌授權(quán)登錄獲取用戶通訊錄的實(shí)現(xiàn)示例

    本文主要介紹了vue調(diào)用谷歌授權(quán)登錄獲取用戶通訊錄的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Vue2 elementui2 中 el-switch 實(shí)現(xiàn)先判斷改變狀態(tài)

    Vue2 elementui2 中 el-switch 實(shí)現(xiàn)先判斷改變狀態(tài)

    本文給大家介紹Vue2 elementui2中el-switch實(shí)現(xiàn)先判斷改變狀態(tài)的相關(guān)知識(shí),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2026-03-03

最新評(píng)論

休宁县| 武川县| 丰城市| 额敏县| 罗田县| 荆州市| 肥乡县| 错那县| 鹿邑县| 巢湖市| 盘锦市| 红安县| 调兵山市| 洪泽县| 台安县| 高雄市| 凤庆县| 新河县| 汕尾市| 正镶白旗| 龙泉市| 铜川市| 莱阳市| 陆良县| 卢湾区| 哈密市| 穆棱市| 东源县| 秦安县| 广汉市| 老河口市| 阿克陶县| 甘孜县| 东阿县| 元阳县| 剑河县| 张家界市| 册亨县| 平度市| 达拉特旗| 闽清县|