🤣 Vue 3 setup 语法糖不能导出模块?编译宏使用指南

678 阅读4分钟

00-setup.png

作者:Mostafa Said

译者:林语冰

资源:VueSchool 官方博客[1]

免责声明:活人翻译,略有删改,仅供粉丝参考!

00. Hello World

大家好,我是大家的 林语冰

如果你接触过 Vue 3 的 <script setup> 语法糖,那你可能会踩过这个雷: “<script setup> 中不能包含 ESM 模块导出”。

每当你从 <script setup> 中导出某些内容时,Vue 就会报错,因为 <script setup> 的幕后机制不允许 ESM 导出。

本文我们会探讨这种报错的原因,<script setup> 的运行机制,以及如何使用 defineExpose 编译宏正确暴露组件数据。

01. 报错原因

在 Vue 3 中,<script setup> 是一种在 SFC(单文件组件)中使用组合式 API 的编译时语法糖。

它的目的是让你的 Vue 组件更精简高效,删除你在旧版 <script> 标记中编写的样板代码。

使用 <script setup> 的组件默认是“封闭”的。这意味着,除非你故意暴露出来,否则你在组件内部定义的任何数据都无法从外部访问。

Vue 强制执行这种封装,从而保持组件逻辑的独立性和模块化。

👇 以下是 Vue 提供用于组件间通信的主要机制:

  • Props:将数据传递给子组件。
  • Emits:发出事件,将数据发送到父组件。
  • Provide/Inject:在祖先组件和后代组件之间深度共享数据。

如果你尝试使用 export 关键字导出数据,Vue 就会报错,因为你破坏了它强制执行的封装。

01-error.png

下面是一个 <script setup 导出报错的示例:

<script setup>
  import { ref } from 'vue'
  const count = ref(0)

  export { count }
  // ❌ 导出会报错
</script>

另一种写法也会报错:

<script setup>
  import { ref } from 'vue'
  // ❌ 导出会报错
  export default {
    setup() {
      const count = ref(0)
      return { count }
    },
  }
</script>

上述两种情况都会报错,原因是 <script setup> 自动将作用域限定为组件实例,并且它不允许导出,因为它其实不是传统的 ESM 模块。

02. 暴露数据的正确方案

为了暴露使用了 <script setup> 的组件的数据,Vue 3 提供了 defineExpose 宏[2]。

这个宏允许你显式定义组件内部状态或方法的可暴露部分,从而可以通过 useTemplateRef()$parent 链访问。

我们可以使用 defineExpose 修复之前的报错:

<script setup>
  import { ref } from 'vue'
  const count = ref(1)

  defineExpose({ count })
  // ✅ 不是 ESM 的 export
</script>

在这个示例中,count 现在通过模板引用 useTemplateRef() 暴露给父组件,从而允许通过受控方式在组件实例外部访问它。

请注意,defineExpose 显式定义可以共享的内容,与试图将数据强制导出到组件外部的 export 语句不同。

03. defineExpose 的用法

defineExpose 允许你选择性地暴露属性、方法,或者在 <script setup> 区块中声明的任何数据。这使你可以完全控制外部可以访问的内容。

我们可以暴露多个值,包括响应式 ref 和计算属性:

<script setup>
  import { ref, computed } from 'vue'
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  defineExpose({
    count,
    doubleCount,
    increment,
  })
</script>

然后,父组件可以通过模板引用访问 countdoubleCountincrement()

<template>
  <ChildComponent ref="child" />
  <button @click="child.increment">增加</button>
  <pre>{{ child }}</pre>
</template>

<script setup>
  import { useTemplateRef } from 'vue'
  import ChildComponent from './components/ChildComponent.vue'

  const child = useTemplateRef('child')
</script>

使用 defineExpose,你可以对组件外部可访问的数据进行细粒度控制,确保你保持适当的封装,同时仍然提供灵活性。

02-click.gif

⛔ 注意,useTemplateRef 只能在 Vue 3.5+ 中可用。如果你使用的是旧版的 Vue,可以使用 模板引用语法[3]。

04. defineExpose 和 useTemplateRef

⛔ 注意,defineExpose() 在组件的模板引用上暴露属性,而不是在从 .vue 文件导入的组件定义上暴露属性。

当你使用 defineExpose() 时,暴露的属性不会添加到组件定义本身,即不会添加到 JS 中导入组件时导入的对象上。

相反,当通过 useTemplateRef() 访问时,这些属性会暴露在组件实例上。

05. defineExpose 的常见场景

👇 当你需要向父组件暴露内部方法或状态,进行复杂交互或自定义组件库时,defineExpose 特别有用:

  • 测试组件状态:有时,你需要访问组件的内部状态,来进行测试或调试。
  • 创建可复用组件:如果你正在构建组件库,你可能希望高级用例可以访问某些方法或属性。
  • 与第三方库交互:你可能需要暴露一些内部组件数据才能与依赖组件实例访问的外部 JS 库一起使用。

高潮总结

Vue 3 的 <script setup> 功能强大,但它不支持 ESM 的 export 语句,因为它旨在保持组件作用域的封装。

👇 要从组件暴露内部数据或方法,正确的方案是使用 defineExpose

  • 封装是关键:Vue 3 的组件被设计为独立组件。
  • defineExpose:当你需要与外界共享内部状态时,defineExpose 提供了一种清晰的声明性方式来执行此操作。
  • 保持模块化:只暴露必要的内容,并保持组件的逻辑清晰且易于管理。

我是大家的 林语冰 👨‍💻,欢迎持续 关注,随时了解海内外前端开发的最新情报。

谢谢的大家点赞、留言和友情转发,我们下期再见~👍

参考文献

[1] VueSchool 官方博客: vueschool.io/articles/vu…

[2] defineExpose 宏: vuejs.org/api/sfc-scr…

[3] 模板引用语法: vueschool.io/lessons/typ…