日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

有點(diǎn)東西,Template可以直接使用Setup語法糖中的變量原來是因?yàn)檫@個(gè)

來源: 責(zé)編: 時(shí)間:2024-06-14 17:40:20 235觀看
導(dǎo)讀前言我們每天寫vue3代碼的時(shí)候都會(huì)使用到setup語法糖,那你知道為什么setup語法糖中的頂層綁定可以在template中直接使用的呢?setup語法糖是如何編譯成setup函數(shù)的呢?本文將圍繞這些問題帶你揭開setup語法糖的神秘面紗。

前言

我們每天寫vue3代碼的時(shí)候都會(huì)使用到setup語法糖,那你知道為什么setup語法糖中的頂層綁定可以在template中直接使用的呢?setup語法糖是如何編譯成setup函數(shù)的呢?本文將圍繞這些問題帶你揭開setup語法糖的神秘面紗。注:本文中使用的vue版本為3.4.19。Bh528資訊網(wǎng)——每日最新資訊28at.com

看個(gè)demo

看個(gè)簡單的demo,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

<template>  <h1>{{ msg }}</h1>  <h2>{{ format(msg) }}</h2>  <h3>{{ title }}</h3>  <Child /></template><script lang="ts" setup>import { ref } from "vue";import Child from "./child.vue";import { format } from "./util.js";const msg = ref("Hello World!");let title;if (msg.value) {  const innerContent = "xxx";  console.log(innerContent);  title = "111";} else {  title = "222";}</script>

在上面的demo中定義了四個(gè)頂層綁定:Child子組件、從util.js文件中導(dǎo)入的format方法、使用ref定義的msg只讀常量、使用let定義的title變量。并且在template中直接使用了這四個(gè)頂層綁定。Bh528資訊網(wǎng)——每日最新資訊28at.com

由于innerContent是在if語句里面的變量,不是<script setup>中的頂層綁定,所以在template中是不能使用innerContent的。Bh528資訊網(wǎng)——每日最新資訊28at.com

但是你有沒有想過為什么<script setup>中的頂層綁定就能在template中使用,而像innerContent這種非頂層綁定就不能在template中使用呢?Bh528資訊網(wǎng)——每日最新資訊28at.com

我們先來看看上面的代碼編譯后的樣子,在之前的文章中已經(jīng)講過很多次如何在瀏覽器中查看編譯后的vue文件,這篇文章就不贅述了。編譯后的代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

import { defineComponent as _defineComponent } from "/node_modules/.vite/deps/vue.js?v=23bfe016";import { ref } from "/node_modules/.vite/deps/vue.js?v=23bfe016";import Child from "/src/components/setupDemo2/child.vue";import { format } from "/src/components/setupDemo2/util.js";const _sfc_main = _defineComponent({  __name: "index",  setup(__props, { expose: __expose }) {    __expose();    const msg = ref("Hello World!");    let title;    if (msg.value) {      const innerContent = "xxx";      console.log(innerContent);      title = "111";    } else {      title = "222";    }    const __returned__ = {      msg,      get title() {        return title;      },      set title(v) {        title = v;      },      Child,      get format() {        return format;      },    };    return __returned__;  },});function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {  // ...省略}_sfc_main.render = _sfc_render;export default _sfc_main;

從上面的代碼中可以看到編譯后已經(jīng)沒有了<script setup>,取而代之的是一個(gè)setup函數(shù),這也就證明了為什么說setup是一個(gè)編譯時(shí)語法糖。Bh528資訊網(wǎng)——每日最新資訊28at.com

setup函數(shù)的參數(shù)有兩個(gè),第一個(gè)參數(shù)為組件的 props。第二個(gè)參數(shù)為Setup 上下文對象,上下文對象暴露了其他一些在 setup 中可能會(huì)用到的值,比如:expose等。Bh528資訊網(wǎng)——每日最新資訊28at.com

再來看看setup函數(shù)中的內(nèi)容,其實(shí)和我們的源代碼差不多,只是多了一個(gè)return。使用return會(huì)將組件中的那四個(gè)頂層綁定暴露出去,所以在template中就可以直接使用<script setup>中的頂層綁定。Bh528資訊網(wǎng)——每日最新資訊28at.com

值的一提的是在return對象中title變量和format函數(shù)有點(diǎn)特別。title、format這兩個(gè)都是屬于訪問器屬性,其他兩個(gè)msg、Child屬于常見的數(shù)據(jù)屬性。Bh528資訊網(wǎng)——每日最新資訊28at.com

title是一個(gè)訪問器屬性,同時(shí)擁有g(shù)et 和 set,讀取title變量時(shí)會(huì)走進(jìn)get中,當(dāng)給title變量賦值時(shí)會(huì)走進(jìn)set中。Bh528資訊網(wǎng)——每日最新資訊28at.com

format也是一個(gè)訪問器屬性,他只擁有g(shù)et ,調(diào)用format函數(shù)時(shí)會(huì)走進(jìn)get中。由于他沒有set,所以不能給format函數(shù)重新賦值。其實(shí)這個(gè)也很容易理解,因?yàn)閒ormat函數(shù)是從util.js文件中import導(dǎo)入的,當(dāng)然不能給他重新賦值。Bh528資訊網(wǎng)——每日最新資訊28at.com

至于在template中是怎么拿到setup函數(shù)返回的對象可以看我的另外一篇文章: Vue 3 的 setup語法糖到底是什么東西?Bh528資訊網(wǎng)——每日最新資訊28at.com

看到這里有的小伙伴會(huì)有疑問了,不是還有一句import { ref } from "vue"也是頂層綁定,為什么里面的ref沒有在setup函數(shù)中使用return暴露出去呢?還有在return對象中是如何將title、format識(shí)別為訪問器屬性呢?Bh528資訊網(wǎng)——每日最新資訊28at.com

在接下來的文章中我會(huì)逐一解答這些問題。Bh528資訊網(wǎng)——每日最新資訊28at.com

compileScript函數(shù)

在之前的 通過debug搞清楚.vue文件怎么變成.js文件文章中已經(jīng)講過了vue的script模塊中的內(nèi)容是由@vue/compiler-sfc包中的compileScript函數(shù)處理的,當(dāng)然你沒看過那篇文章也不會(huì)影響這篇文章的閱讀。Bh528資訊網(wǎng)——每日最新資訊28at.com

首先我們需要啟動(dòng)一個(gè)debug終端。這里以vscode舉例,打開終端然后點(diǎn)擊終端中的+號旁邊的下拉箭頭,在下拉中點(diǎn)擊Javascript Debug Terminal就可以啟動(dòng)一個(gè)debug終端。Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

然后在node_modules中找到vue/compiler-sfc包的compileScript函數(shù)打上斷點(diǎn),compileScript函數(shù)位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js。接下來我們先看看簡化后的compileScript函數(shù)源碼。Bh528資訊網(wǎng)——每日最新資訊28at.com

簡化后的compileScript函數(shù)

在debug終端上面執(zhí)行yarn dev后在瀏覽器中打開對應(yīng)的頁面,比如:http://localhost:5173/ 。此時(shí)斷點(diǎn)就會(huì)走到compileScript函數(shù)中,在我們這個(gè)場景中簡化后的compileScript函數(shù)代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

function compileScript(sfc, options) {  // ---- 第一部分 ----  // 根據(jù)<script setup>中的內(nèi)容生成一個(gè)ctx上下文對象  // 在ctx上下文對象中擁有一些屬性和方法  const ctx = new ScriptCompileContext(sfc, options);  const { source, filename } = sfc;  // 頂層聲明的變量、函數(shù)組成的對象  const setupBindings = Object.create(null);  // script標(biāo)簽中的內(nèi)容開始位置和結(jié)束位置  const startOffset = ctx.startOffset;  const endOffset = ctx.endOffset;  // script setup中的內(nèi)容編譯成的AST抽象語法樹  const scriptSetupAst = ctx.scriptSetupAst;  // ---- 第二部分 ----  // 遍歷<script setup>中的內(nèi)容,處理里面的import語句、頂層變量、函數(shù)、類、枚舉聲明還有宏函數(shù)  for (const node of scriptSetupAst.body) {    if (node.type === "ImportDeclaration") {      // ...省略    }  }  for (const node of scriptSetupAst.body) {    if (      (node.type === "VariableDeclaration" ||        node.type === "FunctionDeclaration" ||        node.type === "ClassDeclaration" ||        node.type === "TSEnumDeclaration") &&      !node.declare    ) {      // 頂層聲明的變量、函數(shù)、類、枚舉聲明組成的setupBindings對象      // 給setupBindings對象賦值,{msg: 'setup-ref'}      // 頂層聲明的變量組成的setupBindings對象      walkDeclaration(        "scriptSetup",        node,        setupBindings,        vueImportAliases,        hoistStatic      );    }  }  // ---- 第三部分 ----  // 移除template中的內(nèi)容和script的開始標(biāo)簽  ctx.s.remove(0, startOffset);  // 移除style中的內(nèi)容和script的結(jié)束標(biāo)簽  ctx.s.remove(endOffset, source.length);  // ---- 第四部分 ----  // 將<script setup>中的頂層綁定的元數(shù)據(jù)存儲(chǔ)到ctx.bindingMetadata對象中  // 為什么要多此一舉存儲(chǔ)一個(gè)bindingMetadata對象呢?答案是setup的return的對象有時(shí)會(huì)直接返回頂層變量,有時(shí)會(huì)返回變量的get方法,有時(shí)會(huì)返回變量的get和set方法,  // 所以才需要一個(gè)bindingMetadata對象來存儲(chǔ)這些頂層綁定的元數(shù)據(jù)。  for (const [key, { isType, imported, source: source2 }] of Object.entries(    ctx.userImports  )) {    if (isType) continue;    ctx.bindingMetadata[key] =      imported === "*" ||      (imported === "default" && source2.endsWith(".vue")) ||      source2 === "vue"        ? "setup-const"        : "setup-maybe-ref";  }  for (const key in setupBindings) {    ctx.bindingMetadata[key] = setupBindings[key];  }  // 生成setup方法的args參數(shù);  let args = `__props`;  const destructureElements =    ctx.hasDefineExposeCall || !options.inlineTemplate      ? [`expose: __expose`]      : [];  if (destructureElements.length) {    args += `, { ${destructureElements.join(", ")} }`;  }  // ---- 第五部分 ----  // 根據(jù)<script setup>中的頂層綁定生成return對象中的內(nèi)容  let returned;  const allBindings = {    ...setupBindings,  };  for (const key in ctx.userImports) {    // 不是引入ts中的類型并且import導(dǎo)入的變量還需要在template中使用    if (!ctx.userImports[key].isType && ctx.userImports[key].isUsedInTemplate) {      allBindings[key] = true;    }  }  returned = `{ `;  for (const key in allBindings) {    if (      allBindings[key] === true &&      ctx.userImports[key].source !== "vue" &&      !ctx.userImports[key].source.endsWith(".vue")    ) {      returned += `get ${key}() { return ${key} }, `;    } else if (ctx.bindingMetadata[key] === "setup-let") {      const setArg = key === "v" ? `_v` : `v`;      returned += `get ${key}() { return ${key} }, set ${key}(${setArg}) { ${key} = ${setArg} }, `;    } else {      returned += `${key}, `;    }  }  returned = returned.replace(/, $/, "") + ` }`;  ctx.s.appendRight(    endOffset,    `const __returned__ = ${returned}Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })return __returned__}`  );  // ---- 第六部分 ----  // 生成setup函數(shù)  ctx.s.prependLeft(    startOffset,    `${genDefaultAs} /*#__PURE__*/${ctx.helper(      `defineComponent`    )}({${def}${runtimeOptions}${hasAwait ? `async ` : ``}setup(${args}) {${exposeCall}`  );  ctx.s.appendRight(endOffset, `})`);  // ---- 第七部分 ----  // 插入import vue語句  if (ctx.helperImports.size > 0) {    ctx.s.prepend(      `import { ${[...ctx.helperImports]        .map((h) => `${h} as _${h}`)        .join(", ")} } from 'vue'`    );  }  return {    // ...省略    bindings: ctx.bindingMetadata,    imports: ctx.userImports,    content: ctx.s.toString(),  };}

首先我們來看看compileScript函數(shù)的第一個(gè)參數(shù)sfc對象,在之前的文章 vue文件是如何編譯為js文件 中我們已經(jīng)講過了sfc是一個(gè)descriptor對象,descriptor對象是由vue文件編譯來的。Bh528資訊網(wǎng)——每日最新資訊28at.com

descriptor對象擁有template屬性、scriptSetup屬性、style屬性,分別對應(yīng)vue文件的<template>模塊、<script setup>模塊、<style>模塊。Bh528資訊網(wǎng)——每日最新資訊28at.com

在我們這個(gè)場景只關(guān)注scriptSetup屬性,sfc.scriptSetup.content的值就是<script setup>模塊中code代碼字符串,Bh528資訊網(wǎng)——每日最新資訊28at.com

sfc.source的值就是vue文件中的源代碼code字符串。sfc.scriptSetup.loc.start.offset為<script setup>中內(nèi)容開始位置,sfc.scriptSetup.loc.end.offset為<script setup>中內(nèi)容結(jié)束位置。詳情查看下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

我們再來看compileScript函數(shù)中的內(nèi)容,在compileScript函數(shù)中包含了從<script setup>語法糖到setup函數(shù)的完整流程。乍一看可能比較難以理解,所以我將其分為七塊。Bh528資訊網(wǎng)——每日最新資訊28at.com

  • 根據(jù)<script setup>中的內(nèi)容生成一個(gè)ctx上下文對象。
  • 遍歷<script setup>中的內(nèi)容,處理里面的import語句、頂層變量、頂層函數(shù)、頂層類、頂層枚舉聲明等。
  • 移除template和style中的內(nèi)容,以及script的開始標(biāo)簽和結(jié)束標(biāo)簽。
  • 將<script setup>中的頂層綁定的元數(shù)據(jù)存儲(chǔ)到ctx.bindingMetadata對象中。
  • 根據(jù)<script setup>中的頂層綁定生成return對象。
  • 生成setup函數(shù)定義
  • 插入import vue語句

在接下來的文章中我將逐個(gè)分析這七塊的內(nèi)容。Bh528資訊網(wǎng)——每日最新資訊28at.com

生成ctx上下文對象

我們來看第一塊的代碼,如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

// 根據(jù)<script setup>中的內(nèi)容生成一個(gè)ctx上下文對象// 在ctx上下文對象中擁有一些屬性和方法const ctx = new ScriptCompileContext(sfc, options);const { source, filename } = sfc;// 頂層聲明的變量、函數(shù)組成的對象const setupBindings = Object.create(null);// script標(biāo)簽中的內(nèi)容開始位置和結(jié)束位置const startOffset = ctx.startOffset;const endOffset = ctx.endOffset;// script setup中的內(nèi)容編譯成的AST抽象語法樹const scriptSetupAst = ctx.scriptSetupAst;

在這一塊的代碼中主要做了一件事,使用ScriptCompileContext構(gòu)造函數(shù)new了一個(gè)ctx上下文對象。在之前的 為什么defineProps宏函數(shù)不需要從vue中import導(dǎo)入?文章中我們已經(jīng)講過了ScriptCompileContext構(gòu)造函數(shù)里面的具體代碼,這篇文章就不贅述了。Bh528資訊網(wǎng)——每日最新資訊28at.com

本文只會(huì)講用到的ScriptCompileContext類中的startOffset、endOffset、scriptSetupAst、userImports、helperImports、bindingMetadata、s等屬性。Bh528資訊網(wǎng)——每日最新資訊28at.com

  • startOffset、endOffset屬性是在ScriptCompileContext類的constructor構(gòu)造函數(shù)中賦值的。其實(shí)就是sfc.scriptSetup.loc.start.offset和sfc.scriptSetup.loc.end.offset,<script setup>中內(nèi)容開始位置和<script setup>中內(nèi)容結(jié)束位置,只是將這兩個(gè)字段塞到ctx上下文中。
  • scriptSetupAst是在ScriptCompileContext類的constructor構(gòu)造函數(shù)中賦值的,他是<script setup>模塊的代碼轉(zhuǎn)換成的AST抽象語法樹。在ScriptCompileContext類的constructor構(gòu)造函數(shù)中會(huì)調(diào)用@babel/parser包的parse函數(shù),以<script setup>中的code代碼字符串為參數(shù)生成AST抽象語法樹。
  • userImports在new一個(gè)ctx上下文對象時(shí)是一個(gè)空對象,用于存儲(chǔ)import導(dǎo)入的頂層綁定內(nèi)容。
  • helperImports同樣在new一個(gè)ctx上下文對象時(shí)是一個(gè)空對象,用于存儲(chǔ)需要從vue中import導(dǎo)入的函數(shù)。
  • bindingMetadata同樣在new一個(gè)ctx上下文對象時(shí)是一個(gè)空對象,用于存儲(chǔ)所有的import頂層綁定和變量頂層綁定的元數(shù)據(jù)。
  • s屬性是在ScriptCompileContext類的constructor構(gòu)造函數(shù)中賦值的,以vue文件中的源代碼code字符串為參數(shù)new了一個(gè)MagicString對象賦值給s屬性。

magic-string是由svelte的作者寫的一個(gè)庫,用于處理字符串的JavaScript庫。它可以讓你在字符串中進(jìn)行插入、刪除、替換等操作,并且能夠生成準(zhǔn)確的sourcemap。Bh528資訊網(wǎng)——每日最新資訊28at.com

MagicString對象中擁有toString、remove、prependLeft、appendRight等方法。s.toString用于生成返回的字符串,我們來舉幾個(gè)例子看看這幾個(gè)方法你就明白了。Bh528資訊網(wǎng)——每日最新資訊28at.com

s.remove( start, end )用于刪除從開始到結(jié)束的字符串:Bh528資訊網(wǎng)——每日最新資訊28at.com

const s = new MagicString('hello word');s.remove(0, 6);s.toString(); // 'word'

s.prependLeft( index, content )用于在指定index的前面插入字符串:Bh528資訊網(wǎng)——每日最新資訊28at.com

const s = new MagicString('hello word');s.prependLeft(5, 'xx');s.toString(); // 'helloxx word'

s.appendRight( index, content )用于在指定index的后面插入字符串:Bh528資訊網(wǎng)——每日最新資訊28at.com

const s = new MagicString('hello word');s.appendRight(5, 'xx');s.toString(); // 'helloxx word'

除了上面說的那幾個(gè)屬性,在這里定義了一個(gè)setupBindings變量。初始值是一個(gè)空對象,用于存儲(chǔ)頂層聲明的變量、函數(shù)等。Bh528資訊網(wǎng)——每日最新資訊28at.com

遍歷

將斷點(diǎn)走到第二部分,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

for (const node of scriptSetupAst.body) {  if (node.type === "ImportDeclaration") {    // ...省略  }}for (const node of scriptSetupAst.body) {  if (    (node.type === "VariableDeclaration" ||      node.type === "FunctionDeclaration" ||      node.type === "ClassDeclaration" ||      node.type === "TSEnumDeclaration") &&    !node.declare  ) {    // 頂層聲明的變量、函數(shù)、類、枚舉聲明組成的setupBindings對象    // 給setupBindings對象賦值,{msg: 'setup-ref'}    // 頂層聲明的變量組成的setupBindings對象    walkDeclaration(      "scriptSetup",      node,      setupBindings,      vueImportAliases,      hoistStatic    );  }}

在這一部分的代碼中使用for循環(huán)遍歷了兩次scriptSetupAst.body,scriptSetupAst.body為script中的代碼對應(yīng)的AST抽象語法樹中body的內(nèi)容,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到scriptSetupAst.body數(shù)組有6項(xiàng),分別對應(yīng)的是script模塊中的6塊代碼。Bh528資訊網(wǎng)——每日最新資訊28at.com

第一個(gè)for循環(huán)中使用if判斷node.type === "ImportDeclaration",也就是判斷是不是import語句。如果是import語句,那么import的內(nèi)容肯定是頂層綁定,需要將import導(dǎo)入的內(nèi)容存儲(chǔ)到ctx.userImports對象中。注:后面會(huì)專門寫一篇文章來講如何收集所有的import導(dǎo)入。Bh528資訊網(wǎng)——每日最新資訊28at.com

通過這個(gè)for循環(huán)已經(jīng)將所有的import導(dǎo)入收集到了ctx.userImports對象中了,在debug終端看看此時(shí)的ctx.userImports,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到在ctx.userImports中收集了三個(gè)import導(dǎo)入,分別是Child組件、format函數(shù)、ref函數(shù)。Bh528資訊網(wǎng)——每日最新資訊28at.com

在里面有幾個(gè)字段需要注意,isUsedInTemplate表示當(dāng)前import導(dǎo)入的東西是不是在template中使用,如果為true那么就需要將這個(gè)import導(dǎo)入塞到return對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

isType表示當(dāng)前import導(dǎo)入的是不是type類型,因?yàn)樵趖s中是可以使用import導(dǎo)入type類型,很明顯type類型也不需要塞到return對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

我們再來看第二個(gè)for循環(huán),同樣也是遍歷scriptSetupAst.body。如果當(dāng)前是變量定義、函數(shù)定義、類定義、ts枚舉定義,這四種類型都屬于頂層綁定(除了import導(dǎo)入以外就只有這四種頂層綁定了)。需要調(diào)用walkDeclaration函數(shù)將這四種頂層綁定收集到setupBindings對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

從前面的scriptSetupAst.body圖中可以看到if模塊的type為IfStatement,明顯不屬于上面的這四種類型,所以不會(huì)執(zhí)行walkDeclaration函數(shù)將里面的innerContent變量收集起來后面再塞到return對象中。這也就解釋了為什么非頂層綁定不能在template中直接使用。Bh528資訊網(wǎng)——每日最新資訊28at.com

我們在debug終端來看看執(zhí)行完第二個(gè)for循環(huán)后setupBindings對象是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到在setupBindings對象中收集msg和title這兩個(gè)頂層變量。其中的setup-ref表示當(dāng)前變量是一個(gè)ref定義的變量,setup-let表示當(dāng)前變量是一個(gè)let定義的變量。Bh528資訊網(wǎng)——每日最新資訊28at.com

移除template模塊和style模塊

接著將斷點(diǎn)走到第三部分,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

ctx.s.remove(0, startOffset);ctx.s.remove(endOffset, source.length);

這塊代碼很簡單,startOffset為<script setup>中的內(nèi)容開始位置,endOffset為<script setup>中的內(nèi)容結(jié)束位置,ctx.s.remove方法為刪除字符串。Bh528資訊網(wǎng)——每日最新資訊28at.com

所以ctx.s.remove(0, startOffset)的作用是:移除template中的內(nèi)容和script的開始標(biāo)簽。Bh528資訊網(wǎng)——每日最新資訊28at.com

ctx.s.remove(endOffset, source.length)的作用是:移除style中的內(nèi)容和script的結(jié)束標(biāo)簽。Bh528資訊網(wǎng)——每日最新資訊28at.com

我們在debug終端看看執(zhí)行這兩個(gè)remove方法之前的code代碼字符串是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到此時(shí)的code代碼字符串和我們源代碼差不多,唯一的區(qū)別就是那幾個(gè)import導(dǎo)入已經(jīng)被提取到script標(biāo)簽外面去了(這個(gè)是在前面第一個(gè)for循環(huán)處理import導(dǎo)入的時(shí)候處理的)。Bh528資訊網(wǎng)——每日最新資訊28at.com

將斷點(diǎn)走到執(zhí)行完這兩個(gè)remove方法之后,在debug終端看看此時(shí)的code代碼字符串,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到執(zhí)行這兩個(gè)remove方法后template模塊、style模塊(雖然本文demo中沒有寫style模塊)、script開始標(biāo)簽、script結(jié)束標(biāo)簽都已經(jīng)被刪除了。唯一剩下的就是script模塊中的內(nèi)容,還有之前提出去的那幾個(gè)import導(dǎo)入。Bh528資訊網(wǎng)——每日最新資訊28at.com

將頂層綁定的元數(shù)據(jù)存儲(chǔ)到ctx.bindingMetadata

接著將斷點(diǎn)走到第四部分,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

for (const [key, { isType, imported, source: source2 }] of Object.entries(  ctx.userImports)) {  if (isType) continue;  ctx.bindingMetadata[key] =    imported === "*" ||    (imported === "default" && source2.endsWith(".vue")) ||    source2 === "vue"      ? "setup-const"      : "setup-maybe-ref";}for (const key in setupBindings) {  ctx.bindingMetadata[key] = setupBindings[key];}// 生成setup函數(shù)的args參數(shù);let args = `__props`;const destructureElements =  ctx.hasDefineExposeCall || !options.inlineTemplate    ? [`expose: __expose`]    : [];if (destructureElements.length) {  args += `, { ${destructureElements.join(", ")} }`;}

上面的代碼主要分為三塊,第一塊為for循環(huán)遍歷前面收集到的ctx.userImports對象。這個(gè)對象里面收集的是所有的import導(dǎo)入,將所有import導(dǎo)入塞到ctx.bindingMetadata對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

第二塊也是for循環(huán)遍歷前面收集的setupBindings對象,這個(gè)對象里面收集的是頂層聲明的變量、函數(shù)、類、枚舉,同樣的將這些頂層綁定塞到ctx.bindingMetadata對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

為什么要多此一舉存儲(chǔ)一個(gè)ctx.bindingMetadata對象呢?Bh528資訊網(wǎng)——每日最新資訊28at.com

答案是setup的return的對象有時(shí)會(huì)直接返回頂層變量(比如demo中的msg常量)。有時(shí)只會(huì)返回變量的訪問器屬性 get(比如demo中的format函數(shù))。有時(shí)會(huì)返回變量的訪問器屬性 get和set(比如demo中的title變量)。所以才需要一個(gè)ctx.bindingMetadata對象來存儲(chǔ)這些頂層綁定的元數(shù)據(jù)。Bh528資訊網(wǎng)——每日最新資訊28at.com

將斷點(diǎn)走到執(zhí)行完這兩個(gè)for循環(huán)的地方,在debug終端來看看此時(shí)收集的ctx.bindingMetadata對象是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

最后一塊代碼也很簡單進(jìn)行字符串拼接生成setup函數(shù)的參數(shù),第一個(gè)參數(shù)為組件的props、第二個(gè)參數(shù)為expose方法組成的對象。如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

生成return對象

接著將斷點(diǎn)走到第五部分,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

let returned;const allBindings = {  ...setupBindings,};for (const key in ctx.userImports) {  // 不是引入ts中的類型并且import導(dǎo)入的變量還需要在template中使用  if (!ctx.userImports[key].isType && ctx.userImports[key].isUsedInTemplate) {    allBindings[key] = true;  }}returned = `{ `;for (const key in allBindings) {  if (    allBindings[key] === true &&    ctx.userImports[key].source !== "vue" &&    !ctx.userImports[key].source.endsWith(".vue")  ) {    returned += `get ${key}() { return ${key} }, `;  } else if (ctx.bindingMetadata[key] === "setup-let") {    const setArg = key === "v" ? `_v` : `v`;    returned += `get ${key}() { return ${key} }, set ${key}(${setArg}) { ${key} = ${setArg} }, `;  } else {    returned += `${key}, `;  }}returned = returned.replace(/, $/, "") + ` }`;ctx.s.appendRight(  endOffset,  `  const __returned__ = ${returned}  Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })  return __returned__  }  `);

這部分的代碼看著很多,其實(shí)邏輯也非常清晰,我也將其分為三塊。Bh528資訊網(wǎng)——每日最新資訊28at.com

在第一塊中首先使用擴(kuò)展運(yùn)算符...setupBindings將setupBindings對象中的屬性合并到allBindings對象中,因?yàn)閟etupBindings對象中存的頂層聲明的變量、函數(shù)、類、枚舉都需要被return出去。Bh528資訊網(wǎng)——每日最新資訊28at.com

然后遍歷ctx.userImports對象,前面講過了ctx.userImports對象中存的是所有的import導(dǎo)入(包括從vue中import導(dǎo)入ref函數(shù))。在循環(huán)里面執(zhí)行了if判斷!ctx.userImports[key].isType && ctx.userImports[key].isUsedInTemplate,這個(gè)判斷的意思是如果當(dāng)前import導(dǎo)入的不是ts的type類型并且import導(dǎo)入的內(nèi)容在template模版中使用了。才會(huì)去執(zhí)行allBindings[key] = true,執(zhí)行后就會(huì)將滿足條件的import導(dǎo)入塞到allBindings對象中。Bh528資訊網(wǎng)——每日最新資訊28at.com

后面生成setup函數(shù)的return對象就是通過遍歷這個(gè)allBindings對象實(shí)現(xiàn)的。這也就解釋了為什么從vue中import導(dǎo)入的ref函數(shù)也是頂層綁定,為什么他沒有被setup函數(shù)返回。因?yàn)橹挥性趖emplate中使用的import導(dǎo)入頂層綁定才會(huì)被setup函數(shù)返回。Bh528資訊網(wǎng)——每日最新資訊28at.com

將斷點(diǎn)走到遍歷ctx.userImports對象之后,在debug終端來看看此時(shí)的allBindings對象是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到此時(shí)的allBindings對象中存了四個(gè)需要return的頂層綁定。Bh528資訊網(wǎng)——每日最新資訊28at.com

接著就是執(zhí)行for循環(huán)遍歷allBindings對象生成return對象的字符串,這循環(huán)中有三個(gè)if判斷條件。我們先來看第一個(gè),代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

if (  allBindings[key] === true &&  ctx.userImports[key].source !== "vue" &&  !ctx.userImports[key].source.endsWith(".vue")) {  returned += `get ${key}() { return ${key} }, `;}

if條件判斷是:如果當(dāng)前import導(dǎo)入不是從vue中,并且也不是import導(dǎo)入一個(gè)vue組件。那么就給return一個(gè)只擁有g(shù)et的訪問器屬性,對應(yīng)我們demo中的就是import { format } from "./util.js"中的format函數(shù)。Bh528資訊網(wǎng)——每日最新資訊28at.com

我們再來看第二個(gè)else if判斷,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

else if (ctx.bindingMetadata[key] === "setup-let") {  const setArg = key === "v" ? `_v` : `v`;  returned += `get ${key}() { return ${key} }, set ${key}(${setArg}) { ${key} = ${setArg} }, `;}

這個(gè)else if條件判斷是:如果當(dāng)前頂層綁定是一個(gè)let定義的變量。那么就給return一個(gè)同時(shí)擁有g(shù)et和set的訪問器屬性,對應(yīng)我們demo中的就是let title"變量。Bh528資訊網(wǎng)——每日最新資訊28at.com

最后就是else,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

else {  returned += `${key}, `;}

這個(gè)else中就是普通的數(shù)據(jù)屬性了,對應(yīng)我們demo中的就是msg變量和Child組件。Bh528資訊網(wǎng)——每日最新資訊28at.com

將斷點(diǎn)走到生成return對象之后,在debug終端來看看此時(shí)生成的return對象是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到此時(shí)已經(jīng)生成了return對象啦。Bh528資訊網(wǎng)——每日最新資訊28at.com

前面我們只生成了return對象,但是還沒將其插入到要生成的code字符串中,所以需要執(zhí)行ctx.s.appendRight方法在末尾插入return的代碼。Bh528資訊網(wǎng)——每日最新資訊28at.com

將斷點(diǎn)走到執(zhí)行完ctx.s.appendRight方法后,在debug終端來看看此時(shí)的code代碼字符串是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到此時(shí)的code代碼字符串中多了一塊return的代碼。Bh528資訊網(wǎng)——每日最新資訊28at.com

生成setup函數(shù)定義

接著將斷點(diǎn)走到第六部分,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

ctx.s.prependLeft(  startOffset,  `${genDefaultAs} /*#__PURE__*/${ctx.helper(    `defineComponent`  )}({${def}${runtimeOptions}${hasAwait ? `async ` : ``}setup(${args}) {${exposeCall}`);ctx.s.appendRight(endOffset, `})`);

這部分的代碼很簡單,調(diào)用ctx.s.prependLeft方法從左邊插入一串代碼。插入的這串代碼就是簡單的字符串拼接,我們在debug終端來看看要插入的代碼是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

是不是覺得上面這塊需要插入的代碼看著很熟悉,他就是編譯后的_sfc_main對象除去setup函數(shù)內(nèi)容的部分。將斷點(diǎn)走到ctx.s.appendRight方法執(zhí)行之后,再來看看此時(shí)的code代碼字符串是什么樣的,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到此時(shí)的setup函數(shù)基本已經(jīng)生成完了。Bh528資訊網(wǎng)——每日最新資訊28at.com

插入import vue語句

上一步生成的code代碼字符串其實(shí)還有一個(gè)問題,在代碼中使用了_defineComponent函數(shù),但是沒有從任何地方去import導(dǎo)入。Bh528資訊網(wǎng)——每日最新資訊28at.com

第七塊的代碼就會(huì)生成缺少的import導(dǎo)入,代碼如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

if (ctx.helperImports.size > 0) {  ctx.s.prepend(    `import { ${[...ctx.helperImports]      .map((h) => `${h} as _${h}`)      .join(", ")} } from 'vue'`  );}

將斷點(diǎn)走到ctx.s.prepend函數(shù)執(zhí)行后,再來看看此時(shí)的code代碼字符串,如下圖:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

從上圖中可以看到已經(jīng)生成了完整的setup函數(shù)啦。Bh528資訊網(wǎng)——每日最新資訊28at.com

總結(jié)

整個(gè)流程圖如下:Bh528資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Bh528資訊網(wǎng)——每日最新資訊28at.com

  • 遍歷<script setup>中的代碼將所有的import導(dǎo)入收集到ctx.userImports對象中。
  • 遍歷<script setup>中的代碼將所有的頂層變量、函數(shù)、類、枚舉收集到setupBindings對象中。
  • 調(diào)用ctx.s.remove方法移除template、style模塊以及script開始標(biāo)簽和結(jié)束標(biāo)簽。
  • 遍歷前面收集的ctx.userImports和setupBindings對象,將所有的頂層綁定元數(shù)據(jù)存儲(chǔ)到bindingMetadata對象中。
  • 遍歷前面收集的ctx.userImports和setupBindings對象,生成return對象中的內(nèi)容。在這一步的時(shí)候會(huì)將沒有在template中使用的import導(dǎo)入給過濾掉,這也就解釋了為什么從vue中導(dǎo)入的ref函數(shù)不包含在return對象中。
  • 調(diào)用ctx.s.prependLeft方法生成setup的函數(shù)定義。
  • 調(diào)用ctx.s.prepend方法生成完整的setup函數(shù)。

本文鏈接:http://m.www897cc.com/showinfo-26-93866-0.html有點(diǎn)東西,Template可以直接使用Setup語法糖中的變量原來是因?yàn)檫@個(gè)

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 從 Prometheus 到 OpenTelemetry:指標(biāo)監(jiān)控的演進(jìn)與實(shí)踐

下一篇: 為什么要推薦使用現(xiàn)代化PHP框架?

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
欧美成人精品一区| 亚洲欧美国产另类| 在线免费观看日本一区| 亚洲国产91| 在线亚洲一区二区| 香蕉国产精品偷在线观看不卡| 午夜精品久久久久久| 亚洲蜜桃精久久久久久久 | 亚洲精品综合在线| 欧美先锋影音| 男人的天堂亚洲在线| 亚洲国产精品久久久久久女王| 亚洲作爱视频| 国产精品一区二区男女羞羞无遮挡| 欧美在线观看一区| 亚洲午夜激情网页| 亚洲国产日韩在线一区模特| 国产一区二区三区在线观看网站 | 午夜日韩福利| 免费看亚洲片| 韩国在线视频一区| 老司机午夜免费精品视频 | 欧美 日韩 国产 一区| 亚洲第一在线综合在线| 麻豆精品视频在线| 国产日韩一区在线| 欧美日韩和欧美的一区二区| 欧美高清hd18日本| 国产精品一区二区视频| 亚洲黄色影片| 欧美在线不卡| 欧美日韩亚洲天堂| 亚洲福利视频免费观看| 亚洲欧美一区二区在线观看| 欧美激情精品久久久| 国产一区视频在线看| 国产精品99久久久久久人| 久久免费黄色| 国产精品永久免费观看| 免费试看一区| 久久国产毛片| 国内欧美视频一区二区| 欧美在线视频免费播放| 怡红院av一区二区三区| 国产精品久久久久久久第一福利| 老司机一区二区三区| 久久成人国产精品| 中日韩美女免费视频网站在线观看| 亚洲国产你懂的| 91久久精品日日躁夜夜躁欧美 | 六十路精品视频| 国产精品视频福利| 亚洲美女黄网| 蜜臀久久99精品久久久久久9| 国产日韩精品一区二区浪潮av| 夜夜嗨av色一区二区不卡| 欧美 日韩 国产精品免费观看| 激情综合色综合久久| 欧美在线免费看| 国产精品揄拍500视频| 国产精品99久久久久久有的能看 | 欧美在线观看一区| 国产欧美激情| 亚洲欧美日本国产有色| 欧美午夜无遮挡| 日韩视频在线永久播放| 欧美大片91| 亚洲黄色三级| 欧美高清hd18日本| 91久久精品一区二区别| 老司机67194精品线观看| 狠狠色噜噜狠狠色综合久| 欧美综合二区| 国产亚洲欧美另类一区二区三区| 午夜激情一区| 国产午夜精品麻豆| 欧美综合国产| 韩国三级电影一区二区| 欧美中文字幕在线| 国产一区二区欧美| 久久精品综合网| 黄色亚洲精品| 麻豆成人在线观看| 亚洲黄色av| 欧美丰满少妇xxxbbb| 亚洲清纯自拍| 欧美日韩国产综合在线| 亚洲深夜激情| 国产精品一区在线播放| 欧美亚洲三区| 激情五月婷婷综合| 美女视频黄 久久| 亚洲精品护士| 欧美日韩一区在线观看视频| 亚洲性线免费观看视频成熟| 国产精品久久久久久久久果冻传媒| 亚洲一区二区在| 国产麻豆一精品一av一免费| 久久精品2019中文字幕| 在线成人h网| 欧美精品一区在线| 亚洲网站在线播放| 国产日韩精品一区观看| 久久久久久色| 亚洲三级免费观看| 国产精品qvod| 久久都是精品| 亚洲激情欧美| 国产精品a级| 久久经典综合| 亚洲日本欧美| 国产精品久久久久久久7电影| 欧美中文字幕视频| 亚洲激情一区二区| 国产精品国产自产拍高清av| 久久激情视频久久| 91久久夜色精品国产网站| 欧美午夜理伦三级在线观看| 欧美在线国产精品| 亚洲精品美女在线观看| 国产精品wwwwww| 久久久久久久久久久一区 | 亚洲精品一区二区三区不| 欧美午夜在线一二页| 久久国产精品久久w女人spa| 亚洲黄色影院| 国产精品亚洲综合一区在线观看 | 久久久亚洲精品一区二区三区 | 亚洲精品小视频| 国产精品入口夜色视频大尺度| 久久久99爱| 99精品视频免费全部在线| 国产女优一区| 欧美国产第一页| 欧美一区二区三区免费观看视频| 亚洲国产日韩欧美综合久久| 国产精品久久亚洲7777| 久久免费午夜影院| 亚洲午夜精品久久久久久浪潮| 国产在线欧美日韩| 欧美系列亚洲系列| 免费在线日韩av| 午夜精品一区二区三区在线| 亚洲欧洲日韩女同| 国产偷久久久精品专区| 欧美啪啪成人vr| 久久欧美中文字幕| 亚洲影视综合| 亚洲精品一区二区三区樱花| 国产午夜精品一区二区三区欧美 | 日韩视频精品在线观看| 国产视频久久久久| 欧美日韩午夜在线| 蜜桃av噜噜一区二区三区| 午夜欧美不卡精品aaaaa| 亚洲精品一区二区三区福利| 韩国av一区| 国产伦精品一区| 欧美日韩在线一区二区| 免费亚洲一区二区| 久久成人资源| 亚洲免费在线观看| 99国产精品自拍| 亚洲国内高清视频| 一区二区视频欧美| 国产三级欧美三级| 国产精品欧美一区二区三区奶水| 欧美激情按摩在线| 久久这里有精品15一区二区三区| 午夜精品久久久久久久99热浪潮 | 黑人中文字幕一区二区三区| 国产精品毛片a∨一区二区三区|国| 欧美黄在线观看| 美女91精品| 久久琪琪电影院| 欧美主播一区二区三区美女 久久精品人 | 久久久亚洲国产天美传媒修理工 | 亚洲一区二区免费看| 99这里只有精品| 亚洲国产综合视频在线观看| 激情欧美日韩| 国产一区二区三区四区hd| 国产精品一二三| 国产精品国产三级国产普通话三级 | 老司机精品视频网站| 久久精品国产综合| 午夜伦理片一区| 亚洲欧美bt| 亚洲欧美激情在线视频| 亚洲色无码播放| 在线一区二区三区做爰视频网站| 亚洲毛片视频| 亚洲麻豆av| 亚洲久久视频| 日韩一区二区高清| 99视频精品免费观看| 日韩午夜激情av| 一本色道久久综合亚洲精品小说| 亚洲美女免费视频| 艳女tv在线观看国产一区| 亚洲最新在线| 亚洲在线免费| 性做久久久久久免费观看欧美| 羞羞视频在线观看欧美|