作者:
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 就会报错,因为你破坏了它强制执行的封装。
下面是一个 <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>
然后,父组件可以通过模板引用访问 count
、doubleCount
和 increment()
:
<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
,你可以对组件外部可访问的数据进行细粒度控制,确保你保持适当的封装,同时仍然提供灵活性。
⛔ 注意,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…