国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

看不懂來打我,vue3如何將template編譯成render函數(shù)

這篇具有很好參考價值的文章主要介紹了看不懂來打我,vue3如何將template編譯成render函數(shù)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

在之前的 通過debug搞清楚.vue文件怎么變成.js文件 文章中我們講過了vue文件是如何編譯成js文件,通過那篇文章我們知道了,template編譯為render函數(shù)底層就是調(diào)用了@vue/compiler-sfc包暴露出來的compileTemplate函數(shù)。由于文章篇幅有限,我們沒有去深入探索compileTemplate函數(shù)是如何將template模塊編譯為render函數(shù),在這篇文章中我們來了解一下。

@vue下面的幾個包

先來介紹一下本文中涉及到vue下的幾個包,分別是:@vue/compiler-sfc、@vue/compiler-dom、@vue/compiler-core。

  • @vue/compiler-sfc:用于編譯vue的SFC文件,這個包依賴vue下的其他包,比如@vue/compiler-dom@vue/compiler-core。這個包一般是給vue-loader?和?@vitejs/plugin-vue使用的。

  • @vue/compiler-dom:這個包專注于瀏覽器端的編譯,處理瀏覽器dom相關(guān)的邏輯都在這里面。

  • @vue/compiler-core:從名字你也能看出來這個包是vue編譯部分的核心,提供了通用的編譯邏輯,不管是瀏覽器端還是服務(wù)端編譯最終都會走到這個包里面來。

先來看個流程圖

先來看一下我畫的template模塊編譯為render函數(shù)這一過程的流程圖,讓你對整個流程有個大概的印象,后面的內(nèi)容看著就不費勁了。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上面的流程圖可以看到整個流程可以分為7步:

  • 執(zhí)行@vue/compiler-sfc包的compileTemplate函數(shù),里面會調(diào)用同一個包的doCompileTemplate函數(shù)。

  • 執(zhí)行@vue/compiler-sfc包的doCompileTemplate函數(shù),里面會調(diào)用@vue/compiler-dom包中的compile函數(shù)。

  • 執(zhí)行@vue/compiler-dom包中的compile函數(shù),里面會對options進(jìn)行了擴(kuò)展,塞了一些處理dom的轉(zhuǎn)換函數(shù)進(jìn)去。分別塞到了options.nodeTransforms數(shù)組和options.directiveTransforms對象中。然后以擴(kuò)展后的options去調(diào)用@vue/compiler-core包的baseCompile函數(shù)。

  • 執(zhí)行@vue/compiler-core包的baseCompile函數(shù),在這個函數(shù)中主要分為4部分。第一部分為檢查傳入的source是不是html字符串,如果是就調(diào)用同一個包下的baseParse函數(shù)生成模版AST抽象語法樹。否則就直接使用傳入的模版AST抽象語法樹。此時node節(jié)點中還有v-forv-model等指令。這里的模版AST抽象語法樹結(jié)構(gòu)和template模塊中的代碼結(jié)構(gòu)是一模一樣的,所以說模版AST抽象語法樹就是對template模塊中的結(jié)構(gòu)進(jìn)行描述。

  • 第二部分為執(zhí)行getBaseTransformPreset函數(shù)拿到@vue/compiler-core包中內(nèi)置的nodeTransformsdirectiveTransforms轉(zhuǎn)換函數(shù)。

  • 第三部分為將傳入的options.nodeTransforms、options.directiveTransforms分別和本地的nodeTransformsdirectiveTransforms進(jìn)行合并得到一堆新的轉(zhuǎn)換函數(shù),和模版AST抽象語法樹一起傳入到transform函數(shù)中執(zhí)行,就會得到轉(zhuǎn)換后的javascript AST抽象語法樹。在這一過程中v-for、v-model等指令已經(jīng)被轉(zhuǎn)換函數(shù)給處理了。得到的javascript AST抽象語法樹的結(jié)構(gòu)和將要生成的render函數(shù)的結(jié)構(gòu)是一模一樣的,所以說javascript AST抽象語法樹就是對render函數(shù)的結(jié)構(gòu)進(jìn)行描述。

  • 第四部分為由于已經(jīng)拿到了和render函數(shù)的結(jié)構(gòu)一模一樣的javascript AST抽象語法樹,只需要在generate函數(shù)中遍歷javascript AST抽象語法樹進(jìn)行字符串拼接就可以得到render函數(shù)了。

關(guān)注公眾號:前端歐陽,解鎖我更多vue干貨文章。還可以加我微信,私信我想看哪些vue原理文章,我會根據(jù)大家的反饋進(jìn)行創(chuàng)作。
看不懂來打我,vue3如何將template編譯成render函數(shù)

@vue/compiler-sfc包的compileTemplate函數(shù)

還是同樣的套路,我們通過debug一個簡單的demo來搞清楚compileTemplate函數(shù)是如何將template編譯成render函數(shù)的。demo代碼如下:

<template>
  <input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const msgList = ref([
  {
    id: 1,
    value: "",
  },
  {
    id: 2,
    value: "",
  },
  {
    id: 3,
    value: "",
  },
]);
</script>

通過debug搞清楚.vue文件怎么變成.js文件 文章中我們已經(jīng)知道了在使用vite的情況下template編譯為render函數(shù)是在node端完成的。所以我們需要啟動一個debug終端,才可以在node端打斷點。這里以vscode舉例,首先我們需要打開終端,然后點擊終端中的+號旁邊的下拉箭頭,在下拉中點擊Javascript Debug Terminal就可以啟動一個debug終端。
看不懂來打我,vue3如何將template編譯成render函數(shù)

compileTemplate函數(shù)在node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js文件中,找到compileTemplate函數(shù)打上斷點,然后在debug終端中執(zhí)行yarn dev(這里是以vite舉例)。在瀏覽器中訪問 http://localhost:5173/,此時斷點就會走到compileTemplate函數(shù)中了。在我們這個場景中compileTemplate函數(shù)簡化后的代碼非常簡單,代碼如下:

function compileTemplate(options) {
  return doCompileTemplate(options);
}

@vue/compiler-sfc包的doCompileTemplate函數(shù)

我們接著將斷點走進(jìn)doCompileTemplate函數(shù)中,看看里面的代碼是什么樣的,簡化后的代碼如下:

import * as CompilerDOM from '@vue/compiler-dom'

function doCompileTemplate({
  source,
  ast: inAST,
  compiler
}) {
  const defaultCompiler = CompilerDOM;
  compiler = compiler || defaultCompiler;
  let { code, ast, preamble, map } = compiler.compile(inAST || source, {
    // ...省略傳入的options
  });
  return { code, ast, preamble, source, errors, tips, map };
}

doCompileTemplate函數(shù)中代碼同樣也很簡單,我們在debug終端中看看compiler、sourceinAST這三個變量的值是長什么樣的。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上圖中我們可以看到此時的compiler變量的值為undefined,source變量的值為template模塊中的代碼,inAST的值為由template模塊編譯而來的AST抽象語法樹。不是說好的要經(jīng)過parse函數(shù)處理后才會得到AST抽象語法樹,為什么這里就已經(jīng)有了AST抽象語法樹?不要著急接著向下看,后面我會解釋。

由于這里的compiler變量的值為undefined,所以compiler會被賦值為CompilerDOM。而CompilerDOM就是@vue/compiler-dom包中暴露的所有內(nèi)容。執(zhí)行compiler.compile函數(shù),就是執(zhí)行@vue/compiler-dom包中的compile函數(shù)。compile函數(shù)接收的第一個參數(shù)為inAST || source,從這里我們知道第一個參數(shù)既可能是AST抽象語法樹,也有可能是template模塊中的html代碼字符串。compile函數(shù)的返回值對象中的code字段就是編譯好的render函數(shù),然后return出去。

@vue/compiler-dom包中的compile函數(shù)

我們接著將斷點走進(jìn)@vue/compiler-dom包中的compile函數(shù),發(fā)現(xiàn)代碼同樣也很簡單,簡化后的代碼如下:

import {
  baseCompile,
} from '@vue/compiler-core'

function compile(src, options = {}) {
  return baseCompile(
    src,
    Object.assign({}, parserOptions, options, {
      nodeTransforms: [
        ...DOMNodeTransforms,
        ...options.nodeTransforms || []
      ],
      directiveTransforms: shared.extend(
        {},
        DOMDirectiveTransforms,
        options.directiveTransforms || {}
      )
    })
  );
}

從上面的代碼中可以看到這里的compile函數(shù)也不是具體實現(xiàn)的地方,在這里調(diào)用的是@vue/compiler-core包的baseCompile函數(shù)??吹竭@里你可能會有疑問,為什么不在上一步的doCompileTemplate函數(shù)中直接調(diào)用@vue/compiler-core包的baseCompile函數(shù),而是要從@vue/compiler-dom包中繞一圈再來調(diào)用呢baseCompile函數(shù)呢?

答案是baseCompile函數(shù)是一個處于@vue/compiler-core包中的API,而@vue/compiler-core可以運行在各種 JavaScript 環(huán)境下,比如瀏覽器端、服務(wù)端等各個平臺。baseCompile函數(shù)接收這些平臺專有的一些options,而我們這里的demo是瀏覽器平臺。所以才需要從@vue/compiler-dom包中繞一圈去調(diào)用@vue/compiler-core包中的baseCompile函數(shù)傳入一些瀏覽器中特有的options。在上面的代碼中我們看到使用DOMNodeTransforms數(shù)組對options中的nodeTransforms屬性進(jìn)行了擴(kuò)展,使用DOMDirectiveTransforms對象對options中的directiveTransforms屬性進(jìn)行了擴(kuò)展。

我們先來看看DOMNodeTransforms數(shù)組:

const DOMNodeTransforms = [
  transformStyle
];

options對象中的nodeTransforms屬性是一個數(shù)組,里面包含了許多transform轉(zhuǎn)換函數(shù)用于處理AST抽象語法樹。經(jīng)過@vue/compiler-domcompile函數(shù)處理后nodeTransforms數(shù)組中多了一個處理style的transformStyle函數(shù)。這里的transformStyle是一個轉(zhuǎn)換函數(shù)用于處理dom上面的style,比如style="color: red"。

我們再來看看DOMDirectiveTransforms對象:

const DOMDirectiveTransforms = {
  cloak: compilerCore.noopDirectiveTransform,
  html: transformVHtml,
  text: transformVText,
  model: transformModel,
  on: transformOn,
  show: transformShow
};

options對象中的directiveTransforms屬性是一個對象,經(jīng)過@vue/compiler-domcompile函數(shù)處理后directiveTransforms對象中增加了處理v-cloakv-html、v-text、v-modelv-onv-show等指令的transform轉(zhuǎn)換函數(shù)。很明顯我們這個demo中input標(biāo)簽上面的v-model指令就是由這里的transformModel轉(zhuǎn)換函數(shù)處理。

你發(fā)現(xiàn)了沒,不管是nodeTransforms數(shù)組還是directiveTransforms對象,增加的transform轉(zhuǎn)換函數(shù)都是處理dom相關(guān)的。經(jīng)過@vue/compiler-domcompile函數(shù)處理后,再調(diào)用baseCompile函數(shù)就有了處理dom相關(guān)的轉(zhuǎn)換函數(shù)了。

@vue/compiler-core包的baseCompile函數(shù)

繼續(xù)將斷點走進(jìn)vue/compiler-core包的baseCompile函數(shù),簡化后的baseCompile函數(shù)代碼如下:

function baseCompile(
  source: string | RootNode,
  options: CompilerOptions = {},
): CodegenResult {
  const ast = isString(source) ? baseParse(source, options) : source

  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

  transform(
    ast,
    Object.assign({}, options, {
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []), // user transforms
      ],
      directiveTransforms: Object.assign(
        {},
        directiveTransforms,
        options.directiveTransforms || {}, // user transforms
      ),
    }),
  )

  return generate(ast, options)
}

我們先來看看baseCompile函數(shù)接收的參數(shù),第一個參數(shù)為source,類型為string | RootNode。這句話的意思是接收的source變量可能是html字符串,也有可能是html字符串編譯后的AST抽象語法樹。再來看看第二個參數(shù)options,我們這里只關(guān)注options.nodeTransforms數(shù)組屬性和options.directiveTransforms對象屬性,這兩個里面都是存了一堆轉(zhuǎn)換函數(shù),區(qū)別就是一個是數(shù)組,一個是對象。

我們再來看看返回值類型CodegenResult,定義如下:

interface CodegenResult {
  code: string
  preamble: string
  ast: RootNode
  map?: RawSourceMap
}

從類型中我們可以看到返回值對象中的code屬性就是編譯好的render函數(shù),而這個返回值就是最后調(diào)用generate函數(shù)返回的。

明白了baseCompile函數(shù)接收的參數(shù)和返回值,我們再來看函數(shù)內(nèi)的代碼。主要分為四塊內(nèi)容:

  • 拿到由html字符串轉(zhuǎn)換成的AST抽象語法樹。

  • 拿到由一堆轉(zhuǎn)換函數(shù)組成的nodeTransforms數(shù)組,和拿到由一堆轉(zhuǎn)換函數(shù)組成的directiveTransforms對象。

  • 執(zhí)行transform函數(shù),使用合并后的nodeTransforms中的所有轉(zhuǎn)換函數(shù)處理AST抽象語法樹中的所有node節(jié)點,使用合并后的directiveTransforms中的轉(zhuǎn)換函數(shù)對會生成props的指令進(jìn)行處理,得到處理后的javascript AST抽象語法樹。

  • 調(diào)用generate函數(shù)根據(jù)上一步處理后的javascript AST抽象語法樹進(jìn)行字符串拼接,拼成render函數(shù)。

獲取AST抽象語法樹

我們先來看第一塊的內(nèi)容,代碼如下:

const ast = isString(source) ? baseParse(source, options) : source

如果傳入的source是html字符串,那就調(diào)用baseParse函數(shù)根據(jù)html字符串生成對應(yīng)的AST抽象語法樹,如果傳入的就是AST抽象語法樹那么就直接賦值給ast變量。為什么這里有這兩種情況呢?

原因是baseCompile函數(shù)可以被直接調(diào)用,也可以像我們這樣由vite的@vitejs/plugin-vue包發(fā)起,經(jīng)過層層調(diào)用后最終執(zhí)行baseCompile函數(shù)。在我們這個場景中,在前面我們就知道了走進(jìn)compileTemplate函數(shù)之前就已經(jīng)有了編譯后的AST抽象語法樹,所以這里不會再調(diào)用baseParse函數(shù)去生成AST抽象語法樹了。那么又是什么時候生成的AST抽象語法樹呢?

在之前的 通過debug搞清楚.vue文件怎么變成.js文件 文章中我們講了調(diào)用createDescriptor函數(shù)會將vue代碼字符串轉(zhuǎn)換為descriptor對象,descriptor對象中擁有template屬性、scriptSetup屬性、styles屬性,分別對應(yīng)vue文件中的template模塊、<script setup>模塊、<style>模塊。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)
createDescriptor函數(shù)在生成template屬性的時候底層同樣也會調(diào)用@vue/compiler-core包的baseParse函數(shù),將template模塊中的html字符串編譯為AST抽象語法樹。

所以在我們這個場景中走到baseCompile函數(shù)時就已經(jīng)有了AST抽象語法樹了,其實底層都調(diào)用的是@vue/compiler-core包的baseParse函數(shù)。

獲取轉(zhuǎn)換函數(shù)

接著將斷點走到第二塊內(nèi)容處,代碼如下:

const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

從上面的代碼可以看到getBaseTransformPreset函數(shù)的返回值是一個數(shù)組,對返回的數(shù)組進(jìn)行解構(gòu),數(shù)組的第一項賦值給nodeTransforms變量,數(shù)組的第二項賦值給directiveTransforms變量。

將斷點走進(jìn)getBaseTransformPreset函數(shù),代碼如下:

function getBaseTransformPreset() {
  return [
    [
      transformOnce,
      transformIf,
      transformMemo,
      transformFor,
      transformFilter,
      trackVForSlotScopes,
      transformExpression
      transformSlotOutlet,
      transformElement,
      trackSlotScopes,
      transformText
    ],
    {
      on: transformOn,
      bind: transformBind,
      model: transformModel
    }
  ];
}

從上面的代碼中不難看出由getBaseTransformPreset函數(shù)的返回值解構(gòu)出來的nodeTransforms變量是一個數(shù)組,數(shù)組中包含一堆transform轉(zhuǎn)換函數(shù),比如處理v-oncev-if、v-memov-for等指令的轉(zhuǎn)換函數(shù)。很明顯我們這個demo中input標(biāo)簽上面的v-for指令就是由這里的transformFor轉(zhuǎn)換函數(shù)處理。

同理由getBaseTransformPreset函數(shù)的返回值解構(gòu)出來的directiveTransforms變量是一個對象,對象中包含處理v-on、v-bind、v-model指令的轉(zhuǎn)換函數(shù)。

經(jīng)過這一步的處理我們就拿到了由一系列轉(zhuǎn)換函數(shù)組成的nodeTransforms數(shù)組,和由一系列轉(zhuǎn)換函數(shù)組成的directiveTransforms對象??吹竭@里我想你可能有一些疑問,為什么nodeTransforms是數(shù)組,directiveTransforms卻是對象呢?為什么有的指令轉(zhuǎn)換轉(zhuǎn)換函數(shù)是在nodeTransforms數(shù)組中,有的卻是在directiveTransforms對象中呢?別著急,我們下面會講。

transform函數(shù)

接著將斷點走到第三塊內(nèi)容,transform函數(shù)處,代碼如下:

transform(
  ast,
  Object.assign({}, options, {
    nodeTransforms: [
      ...nodeTransforms,
      ...(options.nodeTransforms || []), // user transforms
    ],
    directiveTransforms: Object.assign(
      {},
      directiveTransforms,
      options.directiveTransforms || {}, // user transforms
    ),
  }),
)

調(diào)用transform函數(shù)時傳入了兩個參數(shù),第一個參數(shù)為當(dāng)前的AST抽象語法樹,第二個參數(shù)為傳入的options,在options中我們主要看兩個屬性:nodeTransforms數(shù)組和directiveTransforms對象。

nodeTransforms數(shù)組由兩部分組成,分別是上一步拿到的nodeTransforms數(shù)組,和之前在options.nodeTransforms數(shù)組中塞進(jìn)去的轉(zhuǎn)換函數(shù)。

directiveTransforms對象就不一樣了,如果上一步拿到的directiveTransforms對象和options.directiveTransforms對象擁有相同的key,那么后者就會覆蓋前者。以我們這個例子舉例:在上一步中拿到的directiveTransforms對象中有key為model的處理v-model指令的轉(zhuǎn)換函數(shù),但是我們在@vue/compiler-dom包中的compile函數(shù)同樣也給options.directiveTransforms對象中塞了一個key為model的處理v-model指令的轉(zhuǎn)換函數(shù)。那么@vue/compiler-dom包中的v-model轉(zhuǎn)換函數(shù)就會覆蓋上一步中定義的v-model轉(zhuǎn)換函數(shù),那么@vue/compiler-core包中v-model轉(zhuǎn)換函數(shù)是不是就沒用了呢?答案是當(dāng)然有用,在@vue/compiler-dom包中的v-model轉(zhuǎn)換函數(shù)會手動調(diào)用@vue/compiler-core包中v-model轉(zhuǎn)換函數(shù)。這樣設(shè)計的目的是對于一些指令的處理支持不同的平臺傳入不同的轉(zhuǎn)換函數(shù),并且在這些平臺中也可以手動調(diào)用@vue/compiler-core包中提供的指令轉(zhuǎn)換函數(shù),根據(jù)手動調(diào)用的結(jié)果再針對各自平臺進(jìn)行一些特別的處理。

我們先來回憶一下前面demo中的代碼:

<template>
  <input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const msgList = ref([
  {
    id: 1,
    value: "",
  },
  {
    id: 2,
    value: "",
  },
  {
    id: 3,
    value: "",
  },
]);
</script>

接著在debug終端中看看執(zhí)行transform函數(shù)前的AST抽象語法樹是什么樣的,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上圖中可以看到AST抽象語法樹根節(jié)點下面只有一個children節(jié)點,這個children節(jié)點對應(yīng)的就是input標(biāo)簽。在input標(biāo)簽上面有三個props,分別對應(yīng)的是input標(biāo)簽上面的v-for指令、:key屬性、v-model指令。說明在生成AST抽象語法樹的階段不會對指令進(jìn)行處理,而是當(dāng)做普通的屬性一樣使用正則匹配出來,然后塞到props數(shù)組中。

既然在生成AST抽象語法樹的過程中沒有對v-model、v-for等指令進(jìn)行處理,那么又是在什么時候處理的呢?答案是在執(zhí)行transform函數(shù)的時候處理的,在transform函數(shù)中會遞歸遍歷整個AST抽象語法樹,在遍歷每個node節(jié)點時都會將nodeTransforms數(shù)組中的所有轉(zhuǎn)換函數(shù)按照順序取出來執(zhí)行一遍,在執(zhí)行時將當(dāng)前的node節(jié)點和上下文作為參數(shù)傳入。經(jīng)過nodeTransforms數(shù)組中全部的轉(zhuǎn)換函數(shù)處理后,vue提供的許多內(nèi)置指令、語法糖、內(nèi)置組件等也就被處理了,接下來只需要執(zhí)行generate函數(shù)生成render函數(shù)就行了。

nodeTransforms數(shù)組

nodeTransforms 主要是對 node節(jié)點 進(jìn)行操作,可能會替換或者移動節(jié)點。每個node節(jié)點都會將nodeTransforms數(shù)組中的轉(zhuǎn)換函數(shù)按照順序全部執(zhí)行一遍,比如處理v-if指令的transformIf轉(zhuǎn)換函數(shù)就要比處理v-for指令的transformFor函數(shù)先執(zhí)行。所以nodeTransforms是一個數(shù)組,而且數(shù)組中的轉(zhuǎn)換函數(shù)的順序還是有講究的。

在我們這個demo中input標(biāo)簽上面的v-for指令是由nodeTransforms數(shù)組中的transformFor轉(zhuǎn)換函數(shù)處理的,很簡單就可以找到transformFor轉(zhuǎn)換函數(shù)。在函數(shù)開始的地方打一個斷點,代碼就會走到這個斷點中,在debug終端上面看看此時的node節(jié)點是什么樣的,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上圖中可以看到在執(zhí)行transformFor轉(zhuǎn)換函數(shù)之前的node節(jié)點和上一張圖打印的node節(jié)點是一樣的。

我們在執(zhí)行完transformFor轉(zhuǎn)換函數(shù)的地方打一個斷點,看看執(zhí)行完transformFor轉(zhuǎn)換函數(shù)后node節(jié)點變成什么樣了,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上圖我們可以看到經(jīng)過transformFor轉(zhuǎn)換函數(shù)處理后當(dāng)前的node節(jié)點已經(jīng)變成了一個新的node節(jié)點,而原來的input的node節(jié)點變成了這個節(jié)點的children子節(jié)點。新節(jié)點的source.content里存的是v-for="item in msgList"中的msgList變量。新節(jié)點的valueAlias.content里存的是v-for="item in msgList"中的item。我們發(fā)現(xiàn)input子節(jié)點的props數(shù)組現(xiàn)在只有兩項了,原本的v-for指令的props經(jīng)過transformFor轉(zhuǎn)換函數(shù)的處理后已經(jīng)被消費掉了,所以就只有兩項了。

看到這里你可能會有疑問,為什么執(zhí)行transform函數(shù)后會將AST抽象語法樹的結(jié)構(gòu)都改變了呢?

這樣做的目的是在后續(xù)的generate函數(shù)中遞歸遍歷AST抽象語法樹時,只想進(jìn)行字符串拼接就可以拼成render函數(shù)。這里涉及到模版AST抽象語法樹Javascript AST抽象語法樹的概念。

我們來回憶一下template模塊中的代碼:

<template>
<input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

template模版經(jīng)過parse函數(shù)拿到AST抽象語法樹,此時的AST抽象語法樹的結(jié)構(gòu)和template模版的結(jié)構(gòu)是一模一樣的,所以我們稱之為模版AST抽象語法樹模版AST抽象語法樹其實就是描述template模版的結(jié)構(gòu)。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

我們再來看看生成的render函數(shù)的代碼:

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(true), _createElementBlock(
    _Fragment,
    null,
    _renderList($setup.msgList, (item) => {
      return _withDirectives((_openBlock(), _createElementBlock("input", {
        key: item.id,
        "onUpdate:modelValue": ($event) => item.value = $event
      }, null, 8, _hoisted_1)), [
        [_vModelText, item.value]
      ]);
    }),
    128
    /* KEYED_FRAGMENT */
  );
}

很明顯模版AST抽象語法樹無法通過簡單的字符串拼接就可以拼成上面的render函數(shù),所以我們需要一個結(jié)構(gòu)和上面的render函數(shù)一模一樣的Javascript AST抽象語法樹Javascript AST抽象語法樹的作用就是描述render函數(shù)的結(jié)構(gòu)。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

上面這個Javascript AST抽象語法樹就是執(zhí)行transform函數(shù)時根據(jù)模版AST抽象語法樹生成的。有了Javascript AST抽象語法樹后再來執(zhí)行generate函數(shù)時就可以只進(jìn)行簡單的字符串拼接,就能得到render函數(shù)了。

directiveTransforms對象

directiveTransforms對象的作用是對指令進(jìn)行轉(zhuǎn)換,給node節(jié)點生成對應(yīng)的props。比如給子組件上面使用了v-model指令,經(jīng)過directiveTransforms對象中的transformModel轉(zhuǎn)換函數(shù)處理后,v-mode節(jié)點上面就會多兩個props屬性:modelValueonUpdate:modelValue屬性。directiveTransforms對象中的轉(zhuǎn)換函數(shù)不會每次都全部執(zhí)行,而是要node節(jié)點中有對應(yīng)的指令,才會執(zhí)行指令的轉(zhuǎn)換函數(shù)。所以directiveTransforms是對象,而不是數(shù)組。

那為什么有的指令轉(zhuǎn)換函數(shù)在directiveTransforms對象中,有的又在nodeTransforms數(shù)組中呢?

答案是在directiveTransforms對象中的指令全部都是會給node節(jié)點生成props屬性的,那些不生成props屬性的就在nodeTransforms數(shù)組中。

很容易就可以找到@vue/compiler-dom包的transformModel函數(shù),然后打一個斷點,讓斷點走進(jìn)transformModel函數(shù)中,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上面的圖中我們可以看到在@vue/compiler-dom包的transformModel函數(shù)中會調(diào)用@vue/compiler-core包的transformModel函數(shù),拿到返回的baseResult對象后再一些其他操作后直接return baseResult。從左邊的call stack調(diào)用棧中我們可以看到transformModel函數(shù)是由一個buildProps函數(shù)調(diào)用的,看名字你應(yīng)該猜到了buildProps函數(shù)的作用是生成props屬性的。點擊Step Out將斷點跳出transformModel函數(shù),走進(jìn)buildProps函數(shù)中,可以看到buildProps函數(shù)中調(diào)用transformModel函數(shù)的代碼如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上圖中可以看到,name變量的值為modelcontext.directiveTransforms[name]的返回值就是transformModel函數(shù),所以執(zhí)行directiveTransform(prop, node, context)其實就是在執(zhí)行transformModel函數(shù)。在debug終端中可以看到返回的props2是一個數(shù)組,里面存的是v-model指令被處理后生成的props屬性。props屬性數(shù)組中只有一項是onUpdate:modelValue屬性,看到這里有的小伙伴會疑惑了v-model指令不是會生成modelValueonUpdate:modelValue兩個屬性,為什么這里只有一個呢?答案是只有給自定義組件上面使用v-model指令才會生成modelValueonUpdate:modelValue兩個屬性,對于這種原生input標(biāo)簽是不需要生成modelValue屬性的,因為input標(biāo)簽本身是不接收名為modelValue屬性,接收的是value屬性。

其實transform函數(shù)中的內(nèi)容是非常復(fù)雜的,里面包含了vue提供的指令、filter、slot等功能的處理邏輯。transform函數(shù)的設(shè)計高明之處就在于插件化,將處理這些功能的transform轉(zhuǎn)換函數(shù)以插件的形式插入的,這樣邏輯就會非常清晰了。比如我想看v-model指令是如何實現(xiàn)的,我只需要去看對應(yīng)的transformModel轉(zhuǎn)換函數(shù)就行了。又比如哪天vue需要實現(xiàn)一個v-xxx指令,要實現(xiàn)這個指令只需要增加一個transformXxx的轉(zhuǎn)換函數(shù)就行了。

generate函數(shù)

經(jīng)過上一步transform函數(shù)的處理后,已經(jīng)將描述模版結(jié)構(gòu)的模版AST抽象語法樹轉(zhuǎn)換為了描述render函數(shù)結(jié)構(gòu)的Javascript AST抽象語法樹。在前面我們已經(jīng)講過了Javascript AST抽象語法樹就是描述了最終生成render函數(shù)的樣子。所以在generate函數(shù)中只需要遞歸遍歷Javascript AST抽象語法樹,通過字符串拼接的方式就可以生成render函數(shù)了。

將斷點走到執(zhí)行generate函數(shù)前,看看這會兒的Javascript AST抽象語法樹是什么樣的,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

從上面的圖中可以看到Javascript AST模版AST的區(qū)別主要有兩個:

  • node節(jié)點中多了一個codegenNode屬性,這個屬性中存了許多node節(jié)點信息,比如codegenNode.props中就存了keyonUpdate:modelValue屬性的信息。在generate函數(shù)中遍歷每個node節(jié)點時就會讀取這個codegenNode屬性生成render函數(shù)

  • 模版AST中根節(jié)點下面的children節(jié)點就是input標(biāo)簽,但是在這里Javascript AST中卻是根節(jié)點下面的children節(jié)點,再下面的children節(jié)點才是input標(biāo)簽。多了一層節(jié)點,在前面的transform函數(shù)中我們已經(jīng)講了多的這層節(jié)點是由v-for指令生成的,用于給v-for循環(huán)出來的多個節(jié)點當(dāng)父節(jié)點。

將斷點走到generate函數(shù)執(zhí)行之后,可以看到已經(jīng)生成render函數(shù)啦,如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

總結(jié)

現(xiàn)在我們再來看看最開始講的流程圖,我想你應(yīng)該已經(jīng)能將整個流程串起來了。如下圖:
看不懂來打我,vue3如何將template編譯成render函數(shù)

將template編譯為render函數(shù)可以分為7步:

  • 執(zhí)行@vue/compiler-sfc包的compileTemplate函數(shù),里面會調(diào)用同一個包的doCompileTemplate函數(shù)。這一步存在的目的是作為一個入口函數(shù)給外部調(diào)用。

  • 執(zhí)行@vue/compiler-sfc包的doCompileTemplate函數(shù),里面會調(diào)用@vue/compiler-dom包中的compile函數(shù)。這一步存在的目的是入口函數(shù)的具體實現(xiàn)。

  • 執(zhí)行@vue/compiler-dom包中的compile函數(shù),里面會對options進(jìn)行了擴(kuò)展,塞了一些處理dom的轉(zhuǎn)換函數(shù)進(jìn)去。給options.nodeTransforms數(shù)組中塞了處理style的轉(zhuǎn)換函數(shù),和給options.directiveTransforms對象中塞了處理v-cloak、v-htmlv-text、v-model、v-on、v-show等指令的轉(zhuǎn)換函數(shù)。然后以擴(kuò)展后的options去調(diào)用@vue/compiler-core包的baseCompile函數(shù)。

  • 執(zhí)行@vue/compiler-core包的baseCompile函數(shù),在這個函數(shù)中主要分為4部分。第一部分為檢查傳入的source是不是html字符串,如果是就調(diào)用同一個包下的baseParse函數(shù)生成模版AST抽象語法樹。否則就直接使用傳入的模版AST抽象語法樹。此時node節(jié)點中還有v-for、v-model等指令,并沒有被處理掉。這里的模版AST抽象語法樹的結(jié)構(gòu)和template中的結(jié)構(gòu)一模一樣,模版AST抽象語法樹是對template中的結(jié)構(gòu)進(jìn)行描述。

  • 第二部分為執(zhí)行getBaseTransformPreset函數(shù)拿到@vue/compiler-core包中內(nèi)置的nodeTransformsdirectiveTransforms轉(zhuǎn)換函數(shù)。nodeTransforms數(shù)組中的為一堆處理node節(jié)點的轉(zhuǎn)換函數(shù),比如處理v-on指令的transformOnce轉(zhuǎn)換函數(shù)、處理v-if指令的transformIf轉(zhuǎn)換函數(shù)。directiveTransforms對象中存的是對一些“會生成props的指令”進(jìn)行轉(zhuǎn)換的函數(shù),用于給node節(jié)點生成對應(yīng)的props。比如處理v-model指令的transformModel轉(zhuǎn)換函數(shù)。

  • 第三部分為將傳入的options.nodeTransformsoptions.directiveTransforms分別和本地的nodeTransforms、directiveTransforms進(jìn)行合并得到一堆新的轉(zhuǎn)換函數(shù)。其中由于nodeTransforms是數(shù)組,所以在合并的過程中會將options.nodeTransformsnodeTransforms中的轉(zhuǎn)換函數(shù)全部合并進(jìn)去。由于directiveTransforms是對象,如果directiveTransforms對象和options.directiveTransforms對象擁有相同的key,那么后者就會覆蓋前者。然后將合并的結(jié)果和模版AST抽象語法樹一起傳入到transform函數(shù)中執(zhí)行,就可以得到轉(zhuǎn)換后的javascript AST抽象語法樹。在這一過程中v-for、v-model等指令已經(jīng)被轉(zhuǎn)換函數(shù)給處理了。得到的javascript AST抽象語法樹的結(jié)構(gòu)和render函數(shù)的結(jié)構(gòu)一模一樣,javascript AST抽象語法樹就是對render函數(shù)的結(jié)構(gòu)進(jìn)行描述。

  • 第四部分為由于已經(jīng)拿到了和render函數(shù)的結(jié)構(gòu)一模一樣的javascript AST抽象語法樹,只需要在generate函數(shù)中遍歷javascript AST抽象語法樹進(jìn)行字符串拼接就可以得到render函數(shù)了。

關(guān)注公眾號:前端歐陽,解鎖我更多vue干貨文章。還可以加我微信,私信我想看哪些vue原理文章,我會根據(jù)大家的反饋進(jìn)行創(chuàng)作。
看不懂來打我,vue3如何將template編譯成render函數(shù)
看不懂來打我,vue3如何將template編譯成render函數(shù)文章來源地址http://www.zghlxwxcb.cn/news/detail-847744.html

到了這里,關(guān)于看不懂來打我,vue3如何將template編譯成render函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Ubuntn 教你如何一步一步在 Ubuntn 上安裝Samba服務(wù)器詳解(看不懂,你怪我)

    目錄 一、Ubuntu安裝Samba 服務(wù)器 二、Samba服務(wù)器配置 三、啟動和關(guān)閉 四、安裝完成訪問在window界面輸入cmd界面輸入\\\\ip地址,進(jìn)去找到share文件輸入自己設(shè)置的密碼

    2024年01月23日
    瀏覽(38)
  • 十個 C++ 運算符重載示例,看完不懂打我...

    十個 C++ 運算符重載示例,看完不懂打我...

    下面是一些 C++ 運算符重載示例,包括算術(shù)運算符、賦值運算符、邏輯運算符、成員運算符、關(guān)系運算符等等,這些都是使用頻率較高的幾個運算符重載案例。 ?? 所有示例代碼均存放于 GitHub: getiot/cpp-courses/operator_overloading 。 示例 1:一元運算符重載 一元運算符即只對一個

    2023年04月25日
    瀏覽(27)
  • vivado里那些看不懂的原語

    遇到一段代碼 是Intel風(fēng)格的,可以用xilinx的BUFG進(jìn)行替代 // BUFG 分配時鐘專用資源,指定信號走專門的時鐘布線 修改為 布線時候報錯 具體原因未知,只能先把功能打通,所以把相關(guān)的部分注釋掉。 下面是收集的一些資料,來源都做了標(biāo)注。 inout類型的接口,主動添加IOBUF R

    2023年04月08日
    瀏覽(23)
  • 人工智能中一些看不懂的代碼

    人工智能中一些看不懂的代碼

    def forward(self, input: Tensor, hx: Optional[Tensor] = None) - Tuple[Tensor, Tensor]: # noqa: F811 ????????pass forward ,它的第一個參數(shù) input 是一個 Tensor 類型的變量,第二個參數(shù) hx 是一個可選的 Tensor 類型變量,這里使用了 Python 3.7 引入的類型注解語法。 函數(shù)返回值類型是一個由兩個 Tensor 類

    2023年04月21日
    瀏覽(29)
  • 一步步帶你搭建Elasticsearch環(huán)境,還看不懂?

    一步步帶你搭建Elasticsearch環(huán)境,還看不懂?

    1.2 Kibana安裝 1.2.1 Kibana安裝 1.2.2 Kibana插件安裝 1.3 Logstash安裝 1.3.1 Logstash安裝 1.3.2 Logstash導(dǎo)入數(shù)據(jù) 磨刀不誤砍柴工,要學(xué)習(xí)Elasticsearch,首先要搭建起來一套學(xué)習(xí)環(huán)境,本文為手把手教你在MacOS上面搭建Elasticsearch學(xué)習(xí)環(huán)境。 Elasticsearch 是一個分布式、RESTful 風(fēng)格的搜索和數(shù)據(jù)分

    2024年04月14日
    瀏覽(29)
  • 越來越看不懂的企業(yè)數(shù)字化轉(zhuǎn)型……

    越來越看不懂的企業(yè)數(shù)字化轉(zhuǎn)型……

    近日和一做乙方的老友相聚談起了今年的企業(yè)數(shù)字化轉(zhuǎn)型情況,都有一個整體的感受那就是: 越來越看不懂了,有價無市,看似熱鬧,實則觀望。 ? 經(jīng)歷幾年疫情,行業(yè)內(nèi)都普遍認(rèn)為企業(yè)領(lǐng)導(dǎo)對于數(shù)字化的重視程度在提高,畢竟數(shù)字化的技術(shù)能力及所取得的成果在這兩年是

    2024年02月13日
    瀏覽(30)
  • 3年測試技術(shù)面一題都看不懂,字節(jié)面試真的變態(tài).....

    3年測試技術(shù)面一題都看不懂,字節(jié)面試真的變態(tài).....

    最近我的一個讀者朋友去了字節(jié)面試,來給我發(fā)信息吐槽,說字節(jié)的面試太困難了,像他這種三年經(jīng)驗的測試員,在技術(shù)面,居然一題都答不上來,這要多高的水平才能有資格去面試字節(jié)的測試崗位。 確實,字節(jié)作為國內(nèi)互聯(lián)網(wǎng)一線巨頭企業(yè),程序員追求的大廠,面試難點也

    2024年02月05日
    瀏覽(18)
  • 看不懂微信小程序中的文件都是什么?

    小程序的主體部分由3個文件組成,這3個文件必須放在項目的主目錄中,文件名也是固定的,負(fù)責(zé)小程序的整體配置。 整個小程序的入口文件,通過調(diào)用APP()函數(shù)來啟動整個程序。主要用來注冊小程序全局實例,編譯時會和其他頁面的邏輯文件打包成一個JavaScript文件。項目中

    2024年02月08日
    瀏覽(27)
  • 炫云為什么要采用讓人看不懂的GHZ計費?

    炫云為什么要采用讓人看不懂的GHZ計費?

    很多人看到炫云GHZ計費都表示看不懂,覺得麻煩,沒有按核數(shù)、按線程或者按分鐘計費簡單易懂,甚至還被某些同行經(jīng)常拿來攻擊。哪為什么炫云還堅持用GHZ計費呢?哪是因為使用GHZ計費更加公平、透明,且具有硬件無關(guān)性。今天就來和大家詳細(xì)說說炫云為什么要用GHZ計費。

    2024年02月05日
    瀏覽(28)
  • SpringBoot-AOP深入淺出通俗易懂—看不懂你捶鵝

    SpringBoot-AOP深入淺出通俗易懂—看不懂你捶鵝

    目錄 前言 AOP總體思想 AOP圖解 AOP-Aspect-代碼舉例 1、定義Service 2. 定義LoginController 3. 定義UserLoginAspect切面 AOP-Handler攔截器-代碼舉例 1、定義攔截器 2、注冊攔截器 總結(jié) ? ? ? ? Spring最重要的兩個思想就是IOC、AOP,之前的文章SpringBoot自動裝配分析了IOC思想并進(jìn)行了源碼詳解。這

    2024年02月14日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包