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

Vue3.5中響應(yīng)式Props解構(gòu)的編譯原理

 更新時(shí)間:2024年09月21日 16:34:42   作者:前端歐陽  
在Vue3.5版本中,響應(yīng)式Props的解構(gòu)功能正式轉(zhuǎn)正,該功能允許即使在解構(gòu)后也不丟失響應(yīng)性,文通過編譯階段的處理,如何保持解構(gòu)后的props變量仍保持響應(yīng)性,編譯過程中的defineProps宏函數(shù)處理,通過AST和上下文操作實(shí)現(xiàn)變量替換,從而讓解構(gòu)后的變量在運(yùn)行時(shí)維持響應(yīng)式狀態(tài)

前言

在Vue3.5版本中響應(yīng)式 Props 解構(gòu)終于正式轉(zhuǎn)正了,這個(gè)功能之前一直是試驗(yàn)性的。這篇文章來帶你搞清楚,一個(gè)String類型的props經(jīng)過解構(gòu)后明明應(yīng)該是一個(gè)常量了,為什么還沒丟失響應(yīng)式呢?本文中使用的Vue版本為歐陽寫文章時(shí)的最新版Vue3.5.5

看個(gè)demo

我們先來看個(gè)解構(gòu)props的例子。

父組件代碼如下:

<template>
  <ChildDemo name="ouyang" />
</template>

<script setup lang="ts">
import ChildDemo from "./child.vue";
</script>

父組件代碼很簡(jiǎn)單,給子組件傳了一個(gè)名為name的prop,name的值為字符串“ouyang”。

子組件的代碼如下:

<template>
  {{ localName }}
</template>

<script setup lang="ts">
const { name: localName } = defineProps(["name"]);
console.log(localName);
</script>

在子組件中我們將name給解構(gòu)出來了并且賦值給了localName,講道理解構(gòu)出來的localName應(yīng)該是個(gè)常量會(huì)丟失響應(yīng)式的,其實(shí)不會(huì)丟失。

我們?cè)跒g覽器中來看一下編譯后的子組件代碼,很簡(jiǎn)單,直接在network中過濾子組件的名稱即可,如下圖:

從上面可以看到原本的console.log(localName)經(jīng)過編譯后就變成了console.log(__props.name),這樣當(dāng)然就不會(huì)丟失響應(yīng)式了。

我們?cè)賮砜匆粋€(gè)另外一種方式解構(gòu)的例子,這種例子解構(gòu)后就會(huì)丟失響應(yīng)式,子組件代碼如下:

<template>
  {{ localName }}
</template>

<script setup lang="ts">
const props = defineProps(["name"]);
const { name: localName } = props;
console.log(localName);
</script>

在上面的例子中我們不是直接解構(gòu)defineProps的返回值,而是將返回值賦值給props對(duì)象,然后再去解構(gòu)props對(duì)象拿到localName。

從上圖中可以看到這種寫法使用解構(gòu)的localName時(shí),就不會(huì)在編譯階段將其替換為__props.name,這樣的話localName就確實(shí)是一個(gè)普通的常量了,當(dāng)然會(huì)丟失響應(yīng)式。

這是為什么呢?為什么這種解構(gòu)寫法就會(huì)丟失響應(yīng)式呢?別著急,我接下來的文章會(huì)講。

從哪里開下手?

既然這個(gè)是在編譯時(shí)將localName處理成__props.name,那我們當(dāng)然是在編譯時(shí)debug了。

還是一樣的套路,我們?cè)趘scode中啟動(dòng)一個(gè)debug終端。

在之前的 通過debug搞清楚.vue文件怎么變成.js文件文章中我們已經(jīng)知道了vue文件中的<script>模塊實(shí)際是由vue/compiler-sfc包的compileScript函數(shù)處理的。

compileScript函數(shù)位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js

找到compileScript函數(shù)就可以給他打一個(gè)斷點(diǎn)了。

compileScript函數(shù)

debug終端上面執(zhí)行yarn dev后在瀏覽器中打開對(duì)應(yīng)的頁面,比如: http://localhost:5173/ 。此時(shí)斷點(diǎn)就會(huì)走到compileScript函數(shù)中。

在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的compileScript函數(shù)代碼如下:

function compileScript(sfc, options) {
  const ctx = new ScriptCompileContext(sfc, options);
  const scriptSetupAst = ctx.scriptSetupAst;

  // 2.2 process <script setup> body
  for (const node of scriptSetupAst.body) {
    if (node.type === "VariableDeclaration" && !node.declare) {
      const total = node.declarations.length;
      for (let i = 0; i < total; i++) {
        const decl = node.declarations[i];
        const init = decl.init;
        if (init) {
          // defineProps
          const isDefineProps = processDefineProps(ctx, init, decl.id);
        }
      }
    }
  }

  // 3 props destructure transform
  if (ctx.propsDestructureDecl) {
    transformDestructuredProps(ctx);
  }

  return {
    //....
    content: ctx.s.toString(),
  };
}

入?yún)?code>sfc對(duì)象:是一個(gè)descriptor對(duì)象,descriptor對(duì)象是由vue文件編譯來的。descriptor對(duì)象擁有template屬性、scriptSetup屬性、style屬性,分別對(duì)應(yīng)vue文件的<template>模塊、<script setup>模塊、<style>模塊。

ctx上下文對(duì)象:這個(gè)ctx對(duì)象貫穿了整個(gè)script模塊的處理過程,他是根據(jù)vue文件的源代碼初始化出來的。在compileScript函數(shù)中處理script模塊中的內(nèi)容,實(shí)際就是對(duì)ctx對(duì)象進(jìn)行操作。最終ctx.s.toString()就是返回script模塊經(jīng)過編譯后返回的js代碼。

搞清楚了入?yún)?code>sfc對(duì)象和ctx上下文對(duì)象,我們接著來看ctx.scriptSetupAst。從名字我想你也能猜到,他就是script模塊中的代碼對(duì)應(yīng)的AST抽象語法樹。如下圖:

從上圖中可以看到body屬性是一個(gè)數(shù)組,分別對(duì)應(yīng)的是源代碼中的兩行代碼。

數(shù)組的第一項(xiàng)對(duì)應(yīng)的Node節(jié)點(diǎn)類型是VariableDeclaration,他是一個(gè)變量聲明類型的節(jié)點(diǎn)。對(duì)應(yīng)的就是源代碼中的第一行:const { name: localName } = defineProps(["name"])

數(shù)組中的第二項(xiàng)對(duì)應(yīng)的Node節(jié)點(diǎn)類型是ExpressionStatement,他是一個(gè)表達(dá)式類型的節(jié)點(diǎn)。對(duì)應(yīng)的就是源代碼中的第二行:console.log(localName)

我們接著來看compileScript函數(shù)中的外層for循環(huán),也就是遍歷前面講的body數(shù)組,代碼如下:

function compileScript(sfc, options) {
  // ...省略
  // 2.2 process <script setup> body
  for (const node of scriptSetupAst.body) {
    if (node.type === "VariableDeclaration" && !node.declare) {
      const total = node.declarations.length;
      for (let i = 0; i < total; i++) {
        const decl = node.declarations[i];
        const init = decl.init;
        if (init) {
          // defineProps
          const isDefineProps = processDefineProps(ctx, init, decl.id);
        }
      }
    }
  }
  // ...省略
}

我們接著來看外層for循環(huán)里面的第一個(gè)if語句:

if (node.type === "VariableDeclaration" && !node.declare)

這個(gè)if語句的意思是判斷當(dāng)前的節(jié)點(diǎn)類型是不是變量聲明并且確實(shí)有初始化的值。

我們這里的源代碼第一行代碼如下:

const { name: localName } = defineProps(["name"]);

很明顯我們這里是滿足這個(gè)if條件的。

接著在if里面還有一個(gè)內(nèi)層for循環(huán),這個(gè)for循環(huán)是在遍歷node節(jié)點(diǎn)的declarations屬性,這個(gè)屬性是一個(gè)數(shù)組。

declarations數(shù)組屬性表示當(dāng)前變量聲明語句中定義的所有變量,可能會(huì)定義多個(gè)變量,所以他才是一個(gè)數(shù)組。在我們這里只定義了一個(gè)變量localName,所以 declarations數(shù)組中只有一項(xiàng)。

在內(nèi)層for循環(huán),會(huì)去遍歷聲明的變量,然后從變量的節(jié)點(diǎn)中取出init屬性。我想聰明的你從名字應(yīng)該就可以看出來init屬性的作用是什么。

沒錯(cuò),init屬性就是對(duì)應(yīng)的變量的初始化值。在我們這里聲明的localName變量的初始化值就是defineProps(["name"])函數(shù)的返回值。

接著就是判斷init是否存在,也就是判斷變量是否是有初始化值。如果為真,那么就執(zhí)行processDefineProps(ctx, init, decl.id)判斷初始化值是否是在調(diào)用defineProps。換句話說就是判斷當(dāng)前的變量聲明是否是在調(diào)用defineProps宏函數(shù)。

processDefineProps函數(shù)

接著將斷點(diǎn)走進(jìn)processDefineProps函數(shù),在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的代碼如下:

function processDefineProps(ctx, node, declId) {
  if (!isCallOf(node, DEFINE_PROPS)) {
    return processWithDefaults(ctx, node, declId);
  }
  // handle props destructure
  if (declId && declId.type === "ObjectPattern") {
    processPropsDestructure(ctx, declId);
  }
  return true;
}

processDefineProps函數(shù)接收3個(gè)參數(shù)。

第一個(gè)參數(shù)ctx,表示當(dāng)前上下文對(duì)象。

第二個(gè)參數(shù)node,這個(gè)節(jié)點(diǎn)對(duì)應(yīng)的是變量聲明語句中的初始化值的部分。也就是源代碼中的defineProps(["name"])。

第三個(gè)參數(shù)declId,這個(gè)對(duì)應(yīng)的是變量聲明語句中的變量名稱。也就是源代碼中的{ name: localName }

為什么defineProps宏函數(shù)不需要從vue中import導(dǎo)入?文章中我們已經(jīng)講過了這里的第一個(gè)if語句就是用于判斷當(dāng)前是否在執(zhí)行defineProps函數(shù),如果不是那么就直接return false

我們接著來看第二個(gè)if語句,這個(gè)if語句就是判斷當(dāng)前變量聲明是不是“對(duì)象解構(gòu)賦值”。很明顯我們這里就是解構(gòu)出的localName變量,所以代碼將會(huì)走到processPropsDestructure函數(shù)中。

processPropsDestructure函數(shù)

接著將斷點(diǎn)走進(jìn)processPropsDestructure函數(shù),在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的代碼如下:

function processPropsDestructure(ctx, declId) {
  const registerBinding = (
    key: string,
    local: string,
    defaultValue?: Expression
  ) => {
    ctx.propsDestructuredBindings[key] = { local, default: defaultValue };
  };

  for (const prop of declId.properties) {
    const propKey = resolveObjectKey(prop.key);
    registerBinding(propKey, prop.value.name);
  }
}

前面講過了這里的兩個(gè)入?yún)ⅲ?code>ctx表示當(dāng)前上下文對(duì)象。declId表示變量聲明語句中的變量名稱。

首先定義了一個(gè)名為registerBinding的箭頭函數(shù)。

接著就是使用for循環(huán)遍歷declId.properties變量名稱,為什么會(huì)有多個(gè)變量名稱呢?

答案是解構(gòu)的時(shí)候我們可以解構(gòu)一個(gè)對(duì)象的多個(gè)屬性,用于定義多個(gè)變量。

prop屬性如下圖:

從上圖可以看到prop中有兩個(gè)屬性很顯眼,分別是keyvalue。

其中key屬性對(duì)應(yīng)的是解構(gòu)對(duì)象時(shí)從對(duì)象中要提取出的屬性名,因?yàn)槲覀冞@里是解構(gòu)的name屬性,所以上面的值是name

其中value屬性對(duì)應(yīng)的是解構(gòu)對(duì)象時(shí)要賦給的目標(biāo)變量名稱。我們這里是賦值給變量localName,所以上面他的值是localName。

接著來看for循環(huán)中的代碼。

執(zhí)行const propKey = resolveObjectKey(prop.key)拿到要從props對(duì)象中解構(gòu)出的屬性名稱。

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

function resolveObjectKey(node: Node) {
  switch (node.type) {
    case "Identifier":
      return node.name;
  }
  return undefined;
}

如果當(dāng)前是標(biāo)識(shí)符節(jié)點(diǎn),也就是有name屬性。那么就返回name屬性。

最后就是執(zhí)行registerBinding函數(shù)。

registerBinding(propKey, prop.value.name)

第一個(gè)參數(shù)為傳入解構(gòu)對(duì)象時(shí)要提取出的屬性名稱,也就是name。第二個(gè)參數(shù)為解構(gòu)對(duì)象時(shí)要賦給的目標(biāo)變量名稱,也就是localName。

接著將斷點(diǎn)走進(jìn)registerBinding函數(shù),他就在processPropsDestructure函數(shù)里面。

function processPropsDestructure(ctx, declId) {
  const registerBinding = (
    key: string,
    local: string,
    defaultValue?: Expression
  ) => {
    ctx.propsDestructuredBindings[key] = { local, default: defaultValue };
  };
  // ...省略
}

ctx.propsDestructuredBindings是存在ctx上下文中的一個(gè)屬性對(duì)象,這個(gè)對(duì)象里面存的是需要解構(gòu)的多個(gè)props。

對(duì)象的key就是需要解構(gòu)的props。

key對(duì)應(yīng)的value也是一個(gè)對(duì)象,這個(gè)對(duì)象中有兩個(gè)字段。其中的local屬性是解構(gòu)props后要賦給的變量名稱。default屬性是props的默認(rèn)值。

在debug終端來看看此時(shí)的ctx.propsDestructuredBindings對(duì)象是什么樣的,如下圖:

從上圖中就有看到此時(shí)里面已經(jīng)存了一個(gè)name屬性,表示props中的name需要解構(gòu),解構(gòu)出來的變量名為localName,并且默認(rèn)值為undefined。

經(jīng)過這里的處理后在ctx上下文對(duì)象中的ctx.propsDestructuredBindings中就已經(jīng)存了有哪些props需要解構(gòu),以及解構(gòu)后要賦值給哪個(gè)變量。

有了這個(gè)后,后續(xù)只需要將script模塊中的所有代碼遍歷一次,然后找出哪些在使用的變量是props解構(gòu)的變量,比如這里的localName變量將其替換成__props.name即可。

transformDestructuredProps函數(shù)

接著將斷點(diǎn)層層返回,走到最外面的compileScript函數(shù)中。再來回憶一下compileScript函數(shù)的代碼,如下:

function compileScript(sfc, options) {
  const ctx = new ScriptCompileContext(sfc, options);
  const scriptSetupAst = ctx.scriptSetupAst;

  // 2.2 process <script setup> body
  for (const node of scriptSetupAst.body) {
    if (node.type === "VariableDeclaration" && !node.declare) {
      const total = node.declarations.length;
      for (let i = 0; i < total; i++) {
        const decl = node.declarations[i];
        const init = decl.init;
        if (init) {
          // defineProps
          const isDefineProps = processDefineProps(ctx, init, decl.id);
        }
      }
    }
  }

  // 3 props destructure transform
  if (ctx.propsDestructureDecl) {
    transformDestructuredProps(ctx);
  }

  return {
    //....
    content: ctx.s.toString(),
  };
}

經(jīng)過processDefineProps函數(shù)的處理后,ctx.propsDestructureDecl對(duì)象中已經(jīng)存了有哪些變量是由props解構(gòu)出來的。

這里的if (ctx.propsDestructureDecl)條件當(dāng)然滿足,所以代碼會(huì)走到transformDestructuredProps函數(shù)中。

接著將斷點(diǎn)走進(jìn)transformDestructuredProps函數(shù)中,在我們這個(gè)場(chǎng)景中簡(jiǎn)化后的transformDestructuredProps函數(shù)代碼如下:

import { walk } from 'estree-walker'

function transformDestructuredProps(ctx) {
  const rootScope = {};
  let currentScope = rootScope;
  const propsLocalToPublicMap: Record<string, string> = Object.create(null);

  const ast = ctx.scriptSetupAst;

  for (const key in ctx.propsDestructuredBindings) {
    const { local } = ctx.propsDestructuredBindings[key];
    rootScope[local] = true;
    propsLocalToPublicMap[local] = key;
  }

  walk(ast, {
    enter(node: Node) {
      if (node.type === "Identifier") {
        if (currentScope[node.name]) {
          rewriteId(node);
        }
      }
    },
  });

  function rewriteId(id: Identifier) {
    // x --> __props.x
    ctx.s.overwrite(
      id.start! + ctx.startOffset!,
      id.end! + ctx.startOffset!,
      genPropsAccessExp(propsLocalToPublicMap[id.name])
    );
  }
}

transformDestructuredProps函數(shù)中主要分為三塊代碼,分別是for循環(huán)、執(zhí)行walk函數(shù)、定義rewriteId函數(shù)。

我們先來看第一個(gè)for循環(huán),他是遍歷ctx.propsDestructuredBindings對(duì)象。前面我們講過了這個(gè)對(duì)象中存的屬性key是解構(gòu)了哪些props,比如這里就是解構(gòu)了name這個(gè)props。

接著就是使用const { local } = ctx.propsDestructuredBindings[key]拿到解構(gòu)的props在子組件中賦值給了哪個(gè)變量,我們這里是解構(gòu)出來后賦給了localName變量,所以這里的local的值為字符串"localName"。

由于在我們這個(gè)demo中只有兩行代碼,分別是解構(gòu)props和console.log。沒有其他的函數(shù),所以這里的作用域只有一個(gè)。也就是說rootScope始終等于currentScope。

所以這里執(zhí)行rootScope[local] = true后,currentScope對(duì)象中的localName屬性也會(huì)被賦值true。如下圖:

接著就是執(zhí)行propsLocalToPublicMap[local] = key,這里的local存的是解構(gòu)props后賦值給子組件中的變量名稱,key為解構(gòu)了哪個(gè)props。經(jīng)過這行代碼的處理后我們就形成了一個(gè)映射,后續(xù)根據(jù)這個(gè)映射就能輕松的將script模塊中使用解構(gòu)后的localName的地方替換為__props.name。

propsLocalToPublicMap對(duì)象如下圖:

經(jīng)過這個(gè)for循環(huán)的處理后,我們已經(jīng)知道了有哪些變量其實(shí)是經(jīng)過props解構(gòu)來的,以及這些解構(gòu)得到的變量和props的映射關(guān)系。

接下來就是使用walk函數(shù)去遞歸遍歷script模塊中的所有代碼,這個(gè)遞歸遍歷就是遍歷script模塊對(duì)應(yīng)的AST抽象語法樹。

在這里是使用的walk函數(shù)來自于第三方庫estree-walker。

在遍歷語法樹中的某個(gè)節(jié)點(diǎn)時(shí),進(jìn)入的時(shí)候會(huì)觸發(fā)一次enter回調(diào),出去的時(shí)候會(huì)觸發(fā)一次leave回調(diào)。

walk函數(shù)的執(zhí)行代碼如下:

walk(ast, {
  enter(node: Node) {
    if (node.type === "Identifier") {
      if (currentScope[node.name]) {
        rewriteId(node);
      }
    }
  },
});

我們這個(gè)場(chǎng)景中只需要enter進(jìn)入的回調(diào)就行了。

enter回調(diào)中使用外層if判斷當(dāng)前節(jié)點(diǎn)的類型是不是IdentifierIdentifier類型可能是變量名、函數(shù)名等。

我們?cè)创a中的console.log(localName)中的localName就是一個(gè)變量名,當(dāng)遞歸遍歷AST抽象語法樹遍歷到這里的localName對(duì)應(yīng)的節(jié)點(diǎn)時(shí)就會(huì)滿足外層的if條件。

在debug終端來看看此時(shí)滿足外層if條件的node節(jié)點(diǎn),如下圖:

從上面的代碼可以看到此時(shí)的node節(jié)點(diǎn)中對(duì)應(yīng)的變量名為localName。其中startend分別表示localName變量的開始位置和結(jié)束位置。

我們回憶一下前面講過了currentScope對(duì)象中就是存的是有哪些本地的變量是通過props解構(gòu)得到的,這里的localName變量當(dāng)然是通過props解構(gòu)得到的,滿足里層的if條件判斷。

最后代碼會(huì)走進(jìn)rewriteId函數(shù)中,將斷點(diǎn)走進(jìn)rewriteId函數(shù)中,簡(jiǎn)化后的代碼如下:

function rewriteId(id: Identifier) {
  // x --> __props.x
  ctx.s.overwrite(
    id.start + ctx.startOffset,
    id.end + ctx.startOffset,
    genPropsAccessExp(propsLocalToPublicMap[id.name])
  );
}

這里使用了ctx.s.overwrite方法,這個(gè)方法接收三個(gè)參數(shù)。

第一個(gè)參數(shù)是:開始位置,對(duì)應(yīng)的是變量localName在源碼中的開始位置。

第二個(gè)參數(shù)是:結(jié)束位置,對(duì)應(yīng)的是變量localName在源碼中的結(jié)束位置。

第三個(gè)參數(shù)是想要替換成的新內(nèi)容。

第三個(gè)參數(shù)是由genPropsAccessExp函數(shù)返回的,執(zhí)行這個(gè)函數(shù)時(shí)傳入的是propsLocalToPublicMap[id.name]。

前面講過了propsLocalToPublicMap存的是props名稱和解構(gòu)到本地的變量名稱的映射關(guān)系,id.name是解構(gòu)到本地的變量名稱。如下圖:

所以propsLocalToPublicMap[id.name]的執(zhí)行結(jié)果就是name,也就是名為name的props。

接著將斷點(diǎn)走進(jìn)genPropsAccessExp函數(shù),簡(jiǎn)化后的代碼如下:

const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;
function genPropsAccessExp(name: string): string {
  return identRE.test(name)
    ? `__props.${name}`
    : `__props[${JSON.stringify(name)}]`;
}

使用正則表達(dá)式去判斷如果滿足條件就會(huì)返回__props.${name},否則就是返回__props[${JSON.stringify(name)}]。

很明顯我們這里的name當(dāng)然滿足條件,所以genPropsAccessExp函數(shù)會(huì)返回__props.name。

那么什么情況下不會(huì)滿足條件呢?

比如這樣的props:

const { "first-name": firstName } = defineProps(["first-name"]);console.log(firstName);

這種props在這種情況下就會(huì)返回__props["first-name"]

執(zhí)行完genPropsAccessExp函數(shù)后回到ctx.s.overwrite方法的地方,此時(shí)我們已經(jīng)知道了第三個(gè)參數(shù)的值為__props.name。這個(gè)方法的執(zhí)行會(huì)將localName重寫為__props.name

ctx.s.overwrite方法執(zhí)行之前我們來看看此時(shí)的script模塊中的js代碼是什么樣的,如下圖:

從上圖中可以看到此時(shí)的代碼中console.log里面還是localName。

執(zhí)行完ctx.s.overwrite方法后,我們來看看此時(shí)是什么樣的,如下圖:

從上圖中可以看到此時(shí)的代碼中console.log里面已經(jīng)變成了__props.name

這就是在編譯階段將使用到的解構(gòu)localName變量變成__props.name的完整過程。

這會(huì)兒我們來看前面那個(gè)例子解構(gòu)后丟失響應(yīng)式的例子,我想你就很容易想通了。

<script setup lang="ts">
const props = defineProps(["name"]);
const { name: localName } = props;
console.log(localName);
</script>

在處理defineProps宏函數(shù)時(shí),發(fā)現(xiàn)是直接解構(gòu)了返回值才會(huì)進(jìn)行處理。上面這個(gè)例子中沒有直接進(jìn)行解構(gòu),而是將其賦值給props,然后再去解構(gòu)props。這種情況下ctx.propsDestructuredBindings對(duì)象中什么都沒有。

后續(xù)在遞歸遍歷script模塊中的所有代碼,發(fā)現(xiàn)ctx.propsDestructuredBindings對(duì)象中什么都沒有。自然也不會(huì)將localName替換為__props.name,這樣他當(dāng)然就會(huì)丟失響應(yīng)式了。

總結(jié)

在編譯階段首先會(huì)處理宏函數(shù)defineProps,在處理的過程中如果發(fā)現(xiàn)解構(gòu)了defineProps的返回值,那么就會(huì)將解構(gòu)的name屬性,以及name解構(gòu)到本地的localName變量,都全部一起存到ctx.propsDestructuredBindings對(duì)象中。

接下來就會(huì)去遞歸遍歷script模塊中的所有代碼,如果發(fā)現(xiàn)使用的localName變量能夠在ctx.propsDestructuredBindings對(duì)象中找的到。那么就說明這個(gè)localName變量是由props解構(gòu)得到的,就會(huì)將其替換為__props.name,所以使用解構(gòu)后的props依然不會(huì)丟失響應(yīng)式。

另外歐陽寫了一本開源電子書vue3編譯原理揭秘,看完這本書可以讓你對(duì)vue編譯的認(rèn)知有質(zhì)的提升。這本書初、中級(jí)前端能看懂,完全免費(fèi),只求一個(gè)star。

相關(guān)文章

最新評(píng)論