亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

defineProps宏函數(shù)不需要從vue中import導(dǎo)入的原因解析

 更新時(shí)間:2024年07月09日 11:14:35   作者:前端歐陽(yáng)  
這篇文章主要介紹了defineProps宏函數(shù)不需要從vue中import導(dǎo)入的原因解析,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下

前言

我們每天寫vue代碼時(shí)都在用defineProps,但是你有沒有思考過下面這些問題。為什么defineProps不需要import導(dǎo)入?為什么不能在非setup頂層使用defineProps?defineProps是如何將聲明的 props 自動(dòng)暴露給模板?

舉幾個(gè)例子

我們來看幾個(gè)例子,分別對(duì)應(yīng)上面的幾個(gè)問題。

先來看一個(gè)正常的例子,common-child.vue文件代碼如下:

<template>
  <div>content is {{ content }}</div>
</template>
<script setup lang="ts">
defineProps({
  content: String,
});
</script>

我們看到在這個(gè)正常的例子中沒有從任何地方import導(dǎo)入defineProps,直接就可以使用了,并且在template中渲染了props中的content。

我們?cè)賮砜匆粋€(gè)在非setup頂層使用defineProps的例子,if-child.vue文件代碼如下:

<template>
  <div>content is {{ content }}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(10);
if (count.value) {
  defineProps({
    content: String,
  });
}
</script>

代碼跑起來直接就報(bào)錯(cuò)了,提示defineProps is not defined

通過debug搞清楚上面幾個(gè)問題

在我的上一篇文章 vue文件是如何編譯為js文件 中已經(jīng)帶你搞清楚了vue文件中的<script>模塊是如何編譯成瀏覽器可直接運(yùn)行的js代碼,其實(shí)底層就是依靠vue/compiler-sfc包的compileScript函數(shù)。

當(dāng)然如果你還沒看過我的上一篇文章也不影響這篇文章閱讀,這里我會(huì)簡(jiǎn)單說一下。當(dāng)我們import一個(gè)vue文件時(shí)會(huì)觸發(fā)@vitejs/plugin-vue包的transform鉤子函數(shù),在這個(gè)函數(shù)中會(huì)調(diào)用一個(gè)transformMain函數(shù)。transformMain函數(shù)中會(huì)調(diào)用genScriptCode、genTemplateCode、genStyleCode,分別對(duì)應(yīng)的作用是將vue文件中的<script>模塊編譯為瀏覽器可直接運(yùn)行的js代碼、將<template>模塊編譯為render函數(shù)、將<style>模塊編譯為導(dǎo)入css文件的import語(yǔ)句。genScriptCode函數(shù)底層調(diào)用的就是vue/compiler-sfc包的compileScript函數(shù)。

一樣的套路,首先我們?cè)趘scode的打開一個(gè)debug終端。

debug-terminal

然后在node_modules中找到vue/compiler-sfc包的compileScript函數(shù)打上斷點(diǎn),compileScript函數(shù)位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js。在debug終端上面執(zhí)行yarn dev后在瀏覽器中打開對(duì)應(yīng)的頁(yè)面,比如:http://localhost:5173/ 。此時(shí)斷點(diǎn)就會(huì)走到compileScript函數(shù)中,我們?cè)?code>debug中先來看看compileScript函數(shù)的第一個(gè)入?yún)?code>sfc。sfc.filename的值為當(dāng)前編譯的vue文件路徑。由于每編譯一個(gè)vue文件都要走到這個(gè)debug中,現(xiàn)在我們只想debug看看common-child.vue文件,所以為了方便我們?cè)?code>compileScript中加了下面這樣一段代碼,并且去掉了在compileScript函數(shù)中加的斷點(diǎn),這樣就只有編譯common-child.vue文件時(shí)會(huì)走進(jìn)斷點(diǎn)。

debug-common

compileScript函數(shù)

我們?cè)賮砘貞浺幌?code>common-child.vue文件中的script模塊代碼如下:

<script setup lang="ts">
defineProps({
  content: String,
});
</script>

我們接著來看compileScript函數(shù)的入?yún)?code>sfc,在上一篇文章 vue文件是如何編譯為js文件 中我們已經(jīng)講過了sfc是一個(gè)descriptor對(duì)象,descriptor對(duì)象是由vue文件編譯來的。descriptor對(duì)象擁有template屬性、scriptSetup屬性、style屬性,分別對(duì)應(yīng)vue文件的<template>模塊、<script setup>模塊、<style>模塊。在我們這個(gè)場(chǎng)景只關(guān)注scriptSetup屬性,sfc.scriptSetup.content的值就是<script setup>模塊中code代碼字符串,sfc.source的值就是vue文件中的源代碼code字符串。詳情查看下圖:

common-child

compileScript函數(shù)內(nèi)包含了編譯script模塊的所有的邏輯,代碼很復(fù)雜,光是源代碼就接近1000行。這篇文章我們不會(huì)去通讀compileScript函數(shù)的所有功能,只會(huì)講處理defineProps相關(guān)的代碼。下面這個(gè)是我簡(jiǎn)化后的代碼:

function compileScript(sfc, options) {
  const ctx = new ScriptCompileContext(sfc, options);
  const startOffset = ctx.startOffset;
  const endOffset = ctx.endOffset;
  const scriptSetupAst = ctx.scriptSetupAst;
  for (const node of scriptSetupAst.body) {
    if (node.type === "ExpressionStatement") {
      const expr = node.expression;
      if (processDefineProps(ctx, expr)) {
        ctx.s.remove(node.start + startOffset, node.end + startOffset);
      }
    }
    if (node.type === "VariableDeclaration" && !node.declare || node.type.endsWith("Statement")) {
      // ....
    }
  }
  ctx.s.remove(0, startOffset);
  ctx.s.remove(endOffset, source.length);
  let runtimeOptions = ``;
  const propsDecl = genRuntimeProps(ctx);
  if (propsDecl) runtimeOptions += `\n  props: ${propsDecl},`;
  const def =
    (defaultExport ? `\n  ...${normalScriptDefaultVar},` : ``) +
    (definedOptions ? `\n  ...${definedOptions},` : "");
  ctx.s.prependLeft(
    startOffset,
    `\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
      `defineComponent`
    )}({${def}${runtimeOptions}\n  ${
      hasAwait ? `async ` : ``
    }setup(${args}) {\n${exposeCall}`
  );
  ctx.s.appendRight(endOffset, `})`);
  return {
    //....
    content: ctx.s.toString(),
  };
}

compileScript函數(shù)中首先調(diào)用ScriptCompileContext類生成一個(gè)ctx上下文對(duì)象,然后遍歷vue文件的<script setup>模塊生成的AST抽象語(yǔ)法樹。如果節(jié)點(diǎn)類型為ExpressionStatement表達(dá)式語(yǔ)句,那么就執(zhí)行processDefineProps函數(shù),判斷當(dāng)前表達(dá)式語(yǔ)句是否是調(diào)用defineProps函數(shù)。如果是那么就刪除掉defineProps調(diào)用代碼,并且將調(diào)用defineProps函數(shù)時(shí)傳入的參數(shù)對(duì)應(yīng)的node節(jié)點(diǎn)信息存到ctx上下文中。然后從參數(shù)node節(jié)點(diǎn)信息中拿到調(diào)用defineProps宏函數(shù)時(shí)傳入的props參數(shù)的開始位置和結(jié)束位置。再使用slice方法并且傳入開始位置和結(jié)束位置,從<script setup>模塊的代碼字符串中截取到props定義的字符串。然后將截取到的props定義的字符串拼接到vue組件對(duì)象的字符串中,最后再將編譯后的setup函數(shù)代碼字符串拼接到vue組件對(duì)象的字符串中。

ScriptCompileContext

ScriptCompileContext類中我們主要關(guān)注這幾個(gè)屬性:startOffsetendOffset、scriptSetupAst、s。先來看看他的constructor,下面是我簡(jiǎn)化后的代碼。

import MagicString from 'magic-string'
class ScriptCompileContext {
  source = this.descriptor.source
  s = new MagicString(this.source)
  startOffset = this.descriptor.scriptSetup?.loc.start.offset
  endOffset = this.descriptor.scriptSetup?.loc.end.offset
  constructor(descriptor, options) {
    this.s = new MagicString(this.source);
    this.scriptSetupAst = descriptor.scriptSetup && parse(descriptor.scriptSetup.content, this.startOffset);
  }
}

在前面我們已經(jīng)講過了descriptor.scriptSetup對(duì)象就是由vue文件中的<script setup>模塊編譯而來,startOffsetendOffset分別就是descriptor.scriptSetup?.loc.start.offsetdescriptor.scriptSetup?.loc.end.offset,對(duì)應(yīng)的是<script setup>模塊在vue文件中的開始位置和結(jié)束位置。

descriptor.source的值就是vue文件中的源代碼code字符串,這里以descriptor.source為參數(shù)new了一個(gè)MagicString對(duì)象。magic-string是由svelte的作者寫的一個(gè)庫(kù),用于處理字符串的JavaScript庫(kù)。它可以讓你在字符串中進(jìn)行插入、刪除、替換等操作,并且能夠生成準(zhǔn)確的sourcemap。MagicString對(duì)象中擁有toString、remove、prependLeft、appendRight等方法。s.toString用于生成返回的字符串,我們來舉幾個(gè)例子看看這幾個(gè)方法你就明白了。

s.remove( start, end )用于刪除從開始到結(jié)束的字符串:

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

s.prependLeft( index, content )用于在指定index的前面插入字符串:

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

s.appendRight( index, content )用于在指定index的后面插入字符串:

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

我們接著看constructor中的scriptSetupAst屬性是由一個(gè)parse函數(shù)的返回值賦值,parse(descriptor.scriptSetup.content, this.startOffset),parse函數(shù)的代碼如下:

import { parse as babelParse } from '@babel/parser'
function parse(input: string, offset: number): Program {
  try {
    return babelParse(input, {
      plugins,
      sourceType: 'module',
    }).program
  } catch (e: any) {
  }
}

我們?cè)谇懊嬉呀?jīng)講過了descriptor.scriptSetup.content的值就是vue文件中的<script setup>模塊的代碼code字符串,parse函數(shù)中調(diào)用了babel提供的parser函數(shù),將vue文件中的<script setup>模塊的代碼code字符串轉(zhuǎn)換成AST抽象語(yǔ)法樹。

parser

現(xiàn)在我們?cè)賮砜?code>compileScript函數(shù)中的這幾行代碼你理解起來就沒什么難度了,這里的scriptSetupAst變量就是由vue文件中的<script setup>模塊的代碼轉(zhuǎn)換成的AST抽象語(yǔ)法樹。

const ctx = new ScriptCompileContext(sfc, options);
const startOffset = ctx.startOffset;
const endOffset = ctx.endOffset;
const scriptSetupAst = ctx.scriptSetupAst;

流程圖如下:

progress1

processDefineProps函數(shù)

我們接著將斷點(diǎn)走到for循環(huán)開始處,代碼如下:

for (const node of scriptSetupAst.body) {
  if (node.type === "ExpressionStatement") {
    const expr = node.expression;
    if (processDefineProps(ctx, expr)) {
      ctx.s.remove(node.start + startOffset, node.end + startOffset);
    }
  }
}

遍歷AST抽象語(yǔ)法樹,如果當(dāng)前節(jié)點(diǎn)類型為ExpressionStatement表達(dá)式語(yǔ)句,并且processDefineProps函數(shù)執(zhí)行結(jié)果為true就調(diào)用ctx.s.remove方法。這會(huì)兒斷點(diǎn)還在for循環(huán)開始處,在控制臺(tái)執(zhí)行ctx.s.toString()看看當(dāng)前的code代碼字符串。

for-toString

從圖上可以看見此時(shí)toString的執(zhí)行結(jié)果還是和之前的common-child.vue源代碼是一樣的,并且很明顯我們的defineProps是一個(gè)表達(dá)式語(yǔ)句,所以會(huì)執(zhí)行processDefineProps函數(shù)。我們將斷點(diǎn)走到調(diào)用processDefineProps的地方,看到簡(jiǎn)化過的processDefineProps函數(shù)代碼如下:

const DEFINE_PROPS = "defineProps";
function processDefineProps(ctx, node, declId) {
  if (!isCallOf(node, DEFINE_PROPS)) {
    return processWithDefaults(ctx, node, declId);
  }
  ctx.propsRuntimeDecl = node.arguments[0];
  return true;
}

processDefineProps函數(shù)中首先執(zhí)行了isCallOf函數(shù),第一個(gè)參數(shù)傳的是當(dāng)前的AST語(yǔ)法樹中的node節(jié)點(diǎn),第二個(gè)參數(shù)傳的是"defineProps"字符串。從isCallOf的名字中我們就可以猜出他的作用是判斷當(dāng)前的node節(jié)點(diǎn)的類型是不是在調(diào)用defineProps函數(shù),isCallOf的代碼如下:

export function isCallOf(node, test) {
  return !!(
    node &&
    test &&
    node.type === "CallExpression" &&
    node.callee.type === "Identifier" &&
    (typeof test === "string"
      ? node.callee.name === test
      : test(node.callee.name))
  );
}

isCallOf函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù)node是當(dāng)前的node節(jié)點(diǎn),第二個(gè)參數(shù)test是要判斷的函數(shù)名稱,在我們這里是寫死的"defineProps"字符串。我們?cè)?code>debug console中將node.type、node.callee.typenode.callee.name的值打印出來看看。

isCallOf

從圖上看到node.type、node.callee.typenode.callee.name的值后,可以證明我們的猜測(cè)是正確的這里isCallOf的作用是判斷當(dāng)前的node節(jié)點(diǎn)的類型是不是在調(diào)用defineProps函數(shù)。我們這里的node節(jié)點(diǎn)確實(shí)是在調(diào)用defineProps函數(shù),所以isCallOf的執(zhí)行結(jié)果為true,在processDefineProps函數(shù)中是對(duì)isCallOf函數(shù)的執(zhí)行結(jié)果取反。也就是!isCallOf(node, DEFINE_PROPS)的執(zhí)行結(jié)果為false,所以不會(huì)走到return processWithDefaults(ctx, node, declId);。

我們接著來看processDefineProps函數(shù):

function processDefineProps(ctx, node, declId) {
  if (!isCallOf(node, DEFINE_PROPS)) {
    return processWithDefaults(ctx, node, declId);
  }
  ctx.propsRuntimeDecl = node.arguments[0];
  return true;
}

如果當(dāng)前節(jié)點(diǎn)確實(shí)是在執(zhí)行defineProps函數(shù),那么就會(huì)執(zhí)行ctx.propsRuntimeDecl = node.arguments[0];。將當(dāng)前node節(jié)點(diǎn)的第一個(gè)參數(shù)賦值給ctx上下文對(duì)象的propsRuntimeDecl屬性,這里的第一個(gè)參數(shù)其實(shí)就是調(diào)用defineProps函數(shù)時(shí)給傳入的第一個(gè)參數(shù)。為什么寫死成取arguments[0]呢?是因?yàn)?code>defineProps函數(shù)只接收一個(gè)參數(shù),傳入的參數(shù)可以是一個(gè)對(duì)象或者數(shù)組。比如:

const props = defineProps({
  foo: String
})
const props = defineProps(['foo', 'bar'])

記住這個(gè)在ctx上下文上面塞的propsRuntimeDecl屬性,后面生成運(yùn)行時(shí)的props就是根據(jù)propsRuntimeDecl屬性生成的。

至此我們已經(jīng)了解到了processDefineProps中主要做了兩件事:判斷當(dāng)前執(zhí)行的表達(dá)式語(yǔ)句是否是defineProps函數(shù),如果是那么將解析出來的props屬性的信息塞的ctx上下文的propsRuntimeDecl屬性中。

我們這會(huì)兒來看compileScript函數(shù)中的processDefineProps代碼你就能很容易理解了:

for (const node of scriptSetupAst.body) {
  if (node.type === "ExpressionStatement") {
    const expr = node.expression;
    if (processDefineProps(ctx, expr)) {
      ctx.s.remove(node.start + startOffset, node.end + startOffset);
    }
  }
}

遍歷AST語(yǔ)法樹,如果當(dāng)前節(jié)點(diǎn)類型是ExpressionStatement表達(dá)式語(yǔ)句,再執(zhí)行processDefineProps判斷當(dāng)前node節(jié)點(diǎn)是否是執(zhí)行的defineProps函數(shù)。如果是defineProps函數(shù)就調(diào)用ctx.s.remove方法將調(diào)用defineProps函數(shù)的代碼從源代碼中刪除掉。此時(shí)我們?cè)?code>debug console中執(zhí)行ctx.s.toString(),看到我們的code代碼字符串中已經(jīng)沒有了defineProps了:

remove-toString

現(xiàn)在我們能夠回答第一個(gè)問題了:

為什么defineProps不需要import導(dǎo)入?

因?yàn)樵诰幾g過程中如果當(dāng)前AST抽象語(yǔ)法樹的節(jié)點(diǎn)類型是ExpressionStatement表達(dá)式語(yǔ)句,并且調(diào)用的函數(shù)是defineProps,那么就調(diào)用remove方法將調(diào)用defineProps函數(shù)的代碼給移除掉。既然defineProps語(yǔ)句已經(jīng)被移除了,自然也就不需要import導(dǎo)入了defineProps了。

progress2

genRuntimeProps函數(shù)

接著在compileScript函數(shù)中執(zhí)行了兩條remove代碼:

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

這里的startOffset表示script標(biāo)簽中第一個(gè)代碼開始的位置, 所以ctx.s.remove(0, startOffset);的意思是刪除掉包含<script setup>開始標(biāo)簽前面的所有內(nèi)容,也就是刪除掉template模塊的內(nèi)容和<script setup>開始標(biāo)簽。這行代碼執(zhí)行完后我們?cè)倏纯?code>ctx.s.toString()的值:

remove1

接著執(zhí)行ctx.s.remove(endOffset, source.length);,這行代碼的意思是將</script >包含結(jié)束標(biāo)簽后面的內(nèi)容全部刪掉,也就是刪除</script >結(jié)束標(biāo)簽和<style>模塊。這行代碼執(zhí)行完后我們?cè)賮砜纯?code>ctx.s.toString()的值:

remove2

由于我們的common-child.vuescript模塊中只有一個(gè)defineProps函數(shù),所以當(dāng)移除掉template模塊、style模塊、script開始標(biāo)簽和結(jié)束標(biāo)簽后就變成了一個(gè)空字符串。如果你的script模塊中還有其他js業(yè)務(wù)代碼,當(dāng)代碼執(zhí)行到這里后就不會(huì)是空字符串,而是那些js業(yè)務(wù)代碼。

我們接著將compileScript函數(shù)中的斷點(diǎn)走到調(diào)用genRuntimeProps函數(shù)處,代碼如下:

let runtimeOptions = ``;
const propsDecl = genRuntimeProps(ctx);
if (propsDecl) runtimeOptions += `\n  props: ${propsDecl},`;

genRuntimeProps名字你應(yīng)該已經(jīng)猜到了他的作用,根據(jù)ctx上下文生成運(yùn)行時(shí)的props。我們將斷點(diǎn)走到genRuntimeProps函數(shù)內(nèi)部,在我們這個(gè)場(chǎng)景中genRuntimeProps主要執(zhí)行的代碼如下:

function genRuntimeProps(ctx) {
  let propsDecls;
  if (ctx.propsRuntimeDecl) {
    propsDecls = ctx.getString(ctx.propsRuntimeDecl).trim();
  }
  return propsDecls;
}

還記得這個(gè)ctx.propsRuntimeDecl是什么東西嗎?我們?cè)趫?zhí)行processDefineProps函數(shù)判斷當(dāng)前節(jié)點(diǎn)是否為執(zhí)行defineProps函數(shù)的時(shí)候,就將調(diào)用defineProps函數(shù)的參數(shù)node節(jié)點(diǎn)賦值給ctx.propsRuntimeDecl。換句話說ctx.propsRuntimeDecl中擁有調(diào)用defineProps函數(shù)傳入的props參數(shù)中的節(jié)點(diǎn)信息。我們將斷點(diǎn)走進(jìn)ctx.getString函數(shù)看看是如何取出props的:

getString(node, scriptSetup = true) {
  const block = scriptSetup ? this.descriptor.scriptSetup : this.descriptor.script;
  return block.content.slice(node.start, node.end);
}

我們前面已經(jīng)講過了descriptor對(duì)象是由vue文件編譯而來,其中的scriptSetup屬性就是對(duì)應(yīng)的<script setup>模塊。我們這里沒有傳入scriptSetup,所以block的值為this.descriptor.scriptSetup。同樣我們前面也講過scriptSetup.content的值是<script setup>模塊code代碼字符串。請(qǐng)看下圖:

block-content

這里傳入的node節(jié)點(diǎn)就是我們前面存在上下文中ctx.propsRuntimeDecl,也就是在調(diào)用defineProps函數(shù)時(shí)傳入的參數(shù)節(jié)點(diǎn),node.start就是參數(shù)節(jié)點(diǎn)開始的位置,node.end就是參數(shù)節(jié)點(diǎn)的結(jié)束位置。所以使用content.slice方法就可以截取出來調(diào)用defineProps函數(shù)時(shí)傳入的props定義。請(qǐng)看下圖:

slice

現(xiàn)在我們?cè)倩剡^頭來看compileScript函數(shù)中的調(diào)用genRuntimeProps函數(shù)的代碼你就能很容易理解了:

let runtimeOptions = ``;
const propsDecl = genRuntimeProps(ctx);
if (propsDecl) runtimeOptions += `\n  props: ${propsDecl},`;

這里的propsDecl在我們這個(gè)場(chǎng)景中就是使用slice截取出來的props定義,再使用\n props: ${propsDecl},進(jìn)行字符串拼接就得到了runtimeOptions的值。如圖:

runtimeOptions

看到runtimeOptions的值是不是就覺得很熟悉了,又有name屬性,又有props屬性。其實(shí)就是vue組件對(duì)象的code字符串的一部分。name拼接邏輯是在省略的代碼中,我們這篇文章只講props相關(guān)的邏輯,所以name不在這篇文章的討論范圍內(nèi)。

現(xiàn)在我們能夠回答前面提的第三個(gè)問題了。

defineProps是如何將聲明的 props 自動(dòng)暴露給模板?

編譯時(shí)在移除掉defineProps相關(guān)代碼時(shí)會(huì)將調(diào)用defineProps函數(shù)時(shí)傳入的參數(shù)node節(jié)點(diǎn)信息存到ctx上下文中。遍歷完AST抽象語(yǔ)法樹后,然后從上下文中存的參數(shù)node節(jié)點(diǎn)信息中拿到調(diào)用defineProps宏函數(shù)時(shí)傳入props的開始位置和結(jié)束位置。再使用slice方法并且傳入開始位置和結(jié)束位置,從<script setup>模塊的代碼字符串中截取到props定義的字符串。然后將截取到的props定義的字符串拼接到vue組件對(duì)象的字符串中,這樣vue組件對(duì)象中就有了一個(gè)props屬性,這個(gè)props屬性在template模版中可以直接使用。

progress3

拼接成完整的瀏覽器運(yùn)行時(shí)js代碼

我們?cè)賮砜?code>compileScript函數(shù)中的最后一坨代碼;

const def =
  (defaultExport ? `\n  ...${normalScriptDefaultVar},` : ``) +
  (definedOptions ? `\n  ...${definedOptions},` : "");
ctx.s.prependLeft(
  startOffset,
  `\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
    `defineComponent`
  )}({${def}${runtimeOptions}\n  ${
    hasAwait ? `async ` : ``
  }setup(${args}) {\n${exposeCall}`
);
ctx.s.appendRight(endOffset, `})`);
return {
  //....
  content: ctx.s.toString(),
};

這里先調(diào)用了ctx.s.prependLeft方法給字符串開始的地方插入了一串字符串,這串拼接的字符串看著腦瓜子痛,我們直接在debug console上面看看要拼接的字符串是什么樣的:

append

看到這串你應(yīng)該很熟悉,除了前面我們拼接的nameprops之外還有部分setup編譯后的代碼,其實(shí)這就是vue組件對(duì)象的code代碼字符串的一部分。

當(dāng)斷點(diǎn)執(zhí)行完prependLeft方法后,我們?cè)?code>debug console中再看看此時(shí)的ctx.s.toString()的值是什么樣的:

last-toString

從圖上可以看到vue組件對(duì)象上的name屬性、props屬性、setup函數(shù)基本已經(jīng)拼接的差不多了,只差一個(gè)})結(jié)束符號(hào),所以執(zhí)行ctx.s.appendRight(endOffset, }));將結(jié)束符號(hào)插入進(jìn)去。

我們最后再來看看compileScript函數(shù)的返回對(duì)象中的content屬性,也就是ctx.s.toString()content屬性的值就是vue組件中的<script setup>模塊編譯成瀏覽器可執(zhí)行的js代碼字符串。

full

為什么不能在非setup頂層使用defineProps

同樣的套路我們來debug看看if-child.vue文件,先來回憶一下if-child.vue文件的代碼。

<template>
  <div>content is {{ content }}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(10);
if (count.value) {
  defineProps({
    content: String,
  });
}
</script>

將斷點(diǎn)走到compileScript函數(shù)的遍歷AST抽象語(yǔ)法樹的地方,我們看到scriptSetupAst.body數(shù)組中有三個(gè)node節(jié)點(diǎn)。

if-node-list

從圖中我們可以看到這三個(gè)node節(jié)點(diǎn)類型分別是:ImportDeclaration、VariableDeclaration、IfStatement。很明顯這三個(gè)節(jié)點(diǎn)對(duì)應(yīng)的是我們?cè)创a中的import語(yǔ)句、const定義變量、if 模塊。我們?cè)賮砘貞浺幌?code>compileScript函數(shù)中的遍歷AST抽象語(yǔ)法樹的代碼:

function compileScript(sfc, options) {
   // 省略..
  for (const node of scriptSetupAst.body) {
    if (node.type === "ExpressionStatement") {
      const expr = node.expression;
      if (processDefineProps(ctx, expr)) {
        ctx.s.remove(node.start + startOffset, node.end + startOffset);
      }
    }
    if (
      (node.type === "VariableDeclaration" && !node.declare) ||
      node.type.endsWith("Statement")
    ) {
      // ....
    }
  }
  // 省略..
}

從代碼我們就可以看出來第三個(gè)node節(jié)點(diǎn),也就是在if中使用defineProps的代碼,這個(gè)節(jié)點(diǎn)類型為IfStatement,不等于ExpressionStatement,所以代碼不會(huì)走到processDefineProps函數(shù)中,也不會(huì)執(zhí)行remove方法刪除掉調(diào)用defineProps函數(shù)的代碼。當(dāng)代碼運(yùn)行在瀏覽器時(shí)由于我們沒有從任何地方import導(dǎo)入defineProps,當(dāng)然就會(huì)報(bào)錯(cuò)defineProps is not defined

總結(jié)

現(xiàn)在我們能夠回答前面提的三個(gè)問題了。

  • 為什么defineProps不需要import導(dǎo)入?

因?yàn)樵诰幾g過程中如果當(dāng)前AST抽象語(yǔ)法樹的節(jié)點(diǎn)類型是ExpressionStatement表達(dá)式語(yǔ)句,并且調(diào)用的函數(shù)是defineProps,那么就調(diào)用remove方法將調(diào)用defineProps函數(shù)的代碼給移除掉。既然defineProps語(yǔ)句已經(jīng)被移除了,自然也就不需要import導(dǎo)入了defineProps了。

  • 為什么不能在非setup頂層使用defineProps?

因?yàn)樵诜?code>setup頂層使用defineProps的代碼生成AST抽象語(yǔ)法樹后節(jié)點(diǎn)類型就不是ExpressionStatement表達(dá)式語(yǔ)句類型,只有ExpressionStatement表達(dá)式語(yǔ)句類型才會(huì)走到processDefineProps函數(shù)中,并且調(diào)用remove方法將調(diào)用defineProps函數(shù)的代碼給移除掉。當(dāng)代碼運(yùn)行在瀏覽器時(shí)由于我們沒有從任何地方import導(dǎo)入defineProps,當(dāng)然就會(huì)報(bào)錯(cuò)defineProps is not defined。

  • defineProps是如何將聲明的 props 自動(dòng)暴露給模板?

編譯時(shí)在移除掉defineProps相關(guān)代碼時(shí)會(huì)將調(diào)用defineProps函數(shù)時(shí)傳入的參數(shù)node節(jié)點(diǎn)信息存到ctx上下文中。遍歷完AST抽象語(yǔ)法樹后,然后從上下文中存的參數(shù)node節(jié)點(diǎn)信息中拿到調(diào)用defineProps宏函數(shù)時(shí)傳入props的開始位置和結(jié)束位置。再使用slice方法并且傳入開始位置和結(jié)束位置,從<script setup>模塊的代碼字符串中截取到props定義的字符串。然后將截取到的props定義的字符串拼接到vue組件對(duì)象的字符串中,這樣vue組件對(duì)象中就有了一個(gè)props屬性,這個(gè)props屬性在template模版中可以直接使用。

到此這篇關(guān)于defineProps宏函數(shù)不需要從vue中import導(dǎo)入的原因解析的文章就介紹到這了,更多相關(guān)vue import導(dǎo)入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Element Badge標(biāo)記的使用方法

    Element Badge標(biāo)記的使用方法

    這篇文章主要介紹了Element Badge標(biāo)記的使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 如何使用el-cascader組件寫下拉級(jí)聯(lián)多選及全選功能

    如何使用el-cascader組件寫下拉級(jí)聯(lián)多選及全選功能

    這篇文章主要介紹了如何使用el-cascader組件寫下拉級(jí)聯(lián)多選及全選功能,因?yàn)槭怯腥x的功能,所以不能直接使用el-cascader組件,?而是選擇使用el-select組件,?在此組件內(nèi)部使用el-cascader-panel級(jí)聯(lián)面板,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • vue中的適配px2rem示例代碼

    vue中的適配px2rem示例代碼

    這篇文章主要給大家介紹了關(guān)于vue中適配px2rem的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Vue2.0用戶權(quán)限控制解決方案

    Vue2.0用戶權(quán)限控制解決方案

    這篇文章主要介紹了Vue2.0用戶權(quán)限控制解決方法以及源碼說明,一起學(xué)習(xí)下。
    2017-11-11
  • vue中的addEventListener和removeEventListener用法說明

    vue中的addEventListener和removeEventListener用法說明

    這篇文章主要介紹了vue中的addEventListener和removeEventListener用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • element表單驗(yàn)證如何清除校驗(yàn)提示語(yǔ)

    element表單驗(yàn)證如何清除校驗(yàn)提示語(yǔ)

    本文主要介紹了element表單驗(yàn)證如何清除校驗(yàn)提示語(yǔ),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • vue實(shí)現(xiàn)zip文件下載

    vue實(shí)現(xiàn)zip文件下載

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)zip文件下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Vue中CSS?scoped的原理詳細(xì)講解

    Vue中CSS?scoped的原理詳細(xì)講解

    在組件中增加的css加了scoped屬性之后,就在會(huì)在當(dāng)前這個(gè)組件的節(jié)點(diǎn)上增加一個(gè)data-v-xxx屬性,下面這篇文章主要給大家介紹了關(guān)于Vue中CSS?scoped原理的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)及 Springsecurity 按鈕級(jí)別的權(quán)限控制

    Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)及 Springsecurity 按鈕級(jí)別的權(quán)限控制

    這篇文章主要介紹了Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)以及 Springsecurity 按鈕級(jí)別的權(quán)限控制的相關(guān)知識(shí),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-09-09
  • vue實(shí)現(xiàn)簽到日歷效果

    vue實(shí)現(xiàn)簽到日歷效果

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簽到日歷效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評(píng)論