为什么同样的代码在 setup() 和 <script setup> 中效果大不同?

1,910 阅读3分钟

引言

最近在使用 Vue 3 的过程中,我对 <script setup> 语法糖感到特别惊艳。它大幅简化了组件的编写过程。但有一次,我发现同样的代码在 setup() 函数中和 <script setup> 语法糖中表现出来的效果竟然不同!这到底是为什么?经过一个小探索,总算有所了解。这里分享出来,希望能对同样困惑的朋友有所帮助。

现象描述

同样的一段代码,在 setup() 函数和 <script setup> 语法糖中表现出不同的效果。这一现象确实让我相当困惑。于是,我决定通过一段具体的最小复现代码示例来进行探讨和理解。

先来看以下两个例子:

使用 setup() 函数的效果:

20240817_182301.gif

使用 <script setup> 时的效果:

20240817_182329.gif

使用 setup() 函数的代码:

使用 setup() 函数的版本可以在这个链接中在线试验:演示地址

<template>
  <button @click="change">+1</button>
  <div>{{ text }}</div>
  {{ flag }}
</template>

<script lang="ts">
import { ref, defineComponent } from 'vue'
export default defineComponent({
  setup() {
    const flag = ref(false);
    let text = 111;

    const change = () => {
      text++;
      flag.value = !flag.value;
    };

    return {
      flag,
      text,
      change
    };
  }
})
</script>

使用 <script setup> 语法糖的代码:

使用 <script setup> 的版本可以在这个链接中在线试验:演示地址

<template>
  <button @click="change">+1</button>
  <div>{{ text }}</div>
  {{ flag }}
</template>

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

const flag = ref(false);
let text = 111;

const change = () => {
  text++;
  flag.value = !flag.value;
};
</script>

两段代码看上去几乎一模一样,但在运行时却会发现,使用 setup() 函数的版本中页面中 text 的值不会变化,而 <script setup> 版本中页面中 text 的值会变化。为了理解这种现象,我决定查看编译后的产物。

编译后的产物分析

通过上述提供的 演示地址 ,可以方便地运行两种不同版本的代码,并且点击右侧工具栏中的 “JS” 按钮可以查看编译后的产物。

在查看了编译后的代码后,我发现了两种写法在处理变量暴露时的不同之处。这些差异导致了页面行为的变化。

setup() 函数编译后的关键部分:

export default {
  setup() {
    const flag = ref(false);
    let text = 111;

    const change = () => {
      text++;
      flag.value = !flag.value;
    };

    return {
      flag,
      text,  // 这里是值传递
      change
    };
  }
}

在 setup() 函数中,返回的 text 是一个普通的局部变量。通过值传递的方式在返回对象中暴露出来。因此,当 text 的值变化时,由于它不是响应式的,Vue 无法追踪到这种变化,也就不会触发视图的更新。

<script setup> 编译后的关键部分:

const __sfc__ = /*#__PURE__*/defineComponent({
  setup(__props, { expose }) {
    expose();

    const flag = ref(false);
    let text = 111;

    const change = () => {
      text++;
      flag.value = !flag.value;
    };

    return { flag, get text() { return text }, set text(v) { text = v }, change };
  }
});

在 <script setup> 中,编译器会为顶层变量自动生成 getter 和 setter,即使这些变量本身不是响应式的。这样一来,每次渲染时模板会通过 getter 获取最新值,因此表现出类似响应式的效果。

总结

Vue 3 的 <script setup> 语法糖通过自动生成 getter 和 setter,使得顶层变量即使不是响应式,也能在模板中表现出类似响应式的效果。

当然,我的理解可能有所不足或存在偏误,欢迎大家批评指正和交流讨论。希望这篇文章能对你有所帮助,共同提升我们的开发体验! 😊