大家好,我是老码,今天咱们来聊聊 Vue 3 里那个让人又爱又恨的 <script setup>
语法糖。 说它“爱”,是因为它真的能让你的 Vue 组件代码简洁到飞起;说它“恨”,是因为如果你不了解它背后的原理,很容易踩坑。
咱们今天的目标就是:彻底搞懂 <script setup>
,让你用得顺心应手,再也不怕被它“糖”住了!
开胃小菜:<script setup>
是什么?
简单来说,<script setup>
是 Vue 3 提供的一个 语法糖,目的是让咱们用 Composition API 更加方便。 它是一个单文件组件(SFC)中 <script>
标签的一个属性。
如果没有 <script setup>
,你要这样写:
<template>
<div>
{{ count }}
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return {
count,
increment,
};
},
};
</script>
有了 <script setup>
,代码直接变成这样:
<template>
<div>
{{ count }}
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => {
count.value++;
};
</script>
看到没? setup
函数、return
语句,统统不见了! 代码瞬间清爽了很多。 这就是 <script setup>
的魅力所在。
正餐:<script setup>
的神奇之处
<script setup>
为什么能做到这么简洁? 这是因为它在编译时做了很多“魔法”。 咱们来逐一揭秘:
-
自动暴露:变量和函数自动暴露给模板
在
<script setup>
里面声明的顶层变量和函数,都会被自动暴露给模板使用。 换句话说,你不需要再显式地return
它们了。- 变量:
ref
、reactive
、computed
等创建的响应式变量。 - 函数: 普通的 JavaScript 函数。
注意: 只有顶层声明的变量和函数才会被暴露。 如果你在一个函数内部声明变量,那它就只能在函数内部使用,不能在模板中使用。
<template> <div> {{ message }} <button @click="handleClick">Click me</button> </div> </template> <script setup> import { ref } from 'vue'; const message = ref('Hello, world!'); const handleClick = () => { message.value = 'Button clicked!'; }; // 在函数内部声明的变量,无法在模板中使用 const internalVariable = 'This is internal'; </script>
- 变量:
-
自动注册组件
如果你在
<script setup>
中导入了组件,那么这些组件会被自动注册,你可以在模板中直接使用它们,不需要再手动注册components
选项。<template> <div> <MyComponent /> </div> </template> <script setup> import MyComponent from './MyComponent.vue'; </script>
是不是很方便? 但是,要注意命名规范。 Vue 会根据组件的文件名来推断组件的名称。 建议使用 PascalCase 命名组件文件,比如
MyComponent.vue
。 -
defineProps
和defineEmits
<script setup>
提供defineProps
和defineEmits
这两个编译器宏,用来声明组件的 props 和 emits。 它们不需要导入,可以直接使用。-
defineProps
: 声明 props。 它可以接收两种参数:- 对象形式: 类似于 Vue 2 的
props
选项,可以指定 props 的类型、是否必填、默认值等等。 - 数组形式: 简单地声明 props 的名称,类型由 TypeScript 推断(如果使用了 TypeScript)。
<script setup> // 对象形式 const props = defineProps({ message: { type: String, required: true, }, count: { type: Number, default: 0, }, }); // 数组形式 (需要开启 TypeScript 支持) // const props = defineProps(['message', 'count']); </script>
- 对象形式: 类似于 Vue 2 的
-
defineEmits
: 声明 emits。 它也接收两种参数:- 数组形式: 简单地声明 emits 的名称。
- 对象形式 (推荐,需要 TypeScript 支持): 可以对 emit 的参数进行类型校验。
<script setup> // 数组形式 const emit = defineEmits(['update:modelValue', 'custom-event']); // 对象形式 (需要开启 TypeScript 支持) // const emit = defineEmits<{ // (e: 'update:modelValue', value: string): void // (e: 'custom-event', id: number): void // }>() const handleClick = () => { emit('update:modelValue', 'new value'); emit('custom-event', 123); }; </script>
-
-
defineExpose
默认情况下,
<script setup>
中的变量和函数都是私有的,不能在父组件中直接访问。 如果你想在父组件中访问子组件的某些变量或函数,可以使用defineExpose
编译器宏。// 子组件 <script setup> import { ref } from 'vue'; const count = ref(0); const increment = () => { count.value++; }; defineExpose({ count, increment, }); </script> // 父组件 <template> <div> <ChildComponent ref="child" /> <button @click="accessChild">Access Child</button> </div> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const child = ref(null); const accessChild = () => { console.log(child.value.count); child.value.increment(); }; </script>
-
useSlots
和useAttrs
如果你的组件需要使用 slots 或 attrs,可以使用
useSlots
和useAttrs
这两个 API。 它们需要从vue
中导入。<template> <div> <slot name="header"></slot> <p>Default content</p> <slot name="footer"></slot> </div> </template> <script setup> import { useSlots, useAttrs } from 'vue'; const slots = useSlots(); const attrs = useAttrs(); console.log(slots); console.log(attrs); </script>
主菜:<script setup>
的编译过程
<script setup>
的简洁背后,是 Vue 编译器默默地为你做了很多事情。 咱们来看看它的编译过程:
-
解析 SFC: Vue 编译器首先会解析 SFC 文件,将
<template>
、<script>
和<style>
分别提取出来。 -
转换
<script setup>
: 这是最关键的一步。 编译器会将<script setup>
中的代码转换成标准的 JavaScript 代码。 主要包括以下几个步骤:- 包裹在
setup
函数中: 将<script setup>
中的所有代码包裹在一个setup
函数中。 - 处理顶层声明:
- 将顶层变量和函数转换为
ref
或reactive
变量(如果需要)。 - 将这些变量和函数添加到
setup
函数的返回值中,以便暴露给模板。
- 将顶层变量和函数转换为
- 处理
defineProps
和defineEmits
: 将defineProps
和defineEmits
转换为props
和emits
选项。 - 处理
defineExpose
: 将defineExpose
转换为expose
选项。 - 自动注册组件: 自动注册导入的组件。
- 包裹在
-
生成渲染函数: 根据
<template>
生成渲染函数。 -
输出 JavaScript 代码: 将转换后的 JavaScript 代码输出。
为了更直观地理解这个过程,咱们来看一个例子:
<template>
<div>
{{ message }}
<button @click="handleClick">Click me</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('Hello, world!');
const handleClick = () => {
message.value = 'Button clicked!';
};
</script>
经过编译后,可能会变成类似这样的代码:
import { ref, defineComponent } from 'vue';
export default defineComponent({
setup() {
const message = ref('Hello, world!');
const handleClick = () => {
message.value = 'Button clicked!';
};
return {
message,
handleClick,
};
},
render: // 省略渲染函数
});
可以看到,<script setup>
中的代码被包裹在 setup
函数中,message
和 handleClick
被添加到 setup
函数的返回值中。
甜点:<script setup>
的注意事项
虽然 <script setup>
很好用,但是也有一些需要注意的地方:
-
不能和
export default
并存:<script setup>
已经隐式地导出了组件,所以不能再使用export default
。 如果你需要导出一些额外的东西,可以使用单独的<script>
标签。<template> <div> {{ message }} </div> </template> <script setup> import { ref } from 'vue'; const message = ref('Hello, world!'); </script> <script> // 可以导出一些额外的配置 export default { name: 'MyComponent', }; </script>
-
顶层
await
:<script setup>
支持顶层await
,这意味着你可以在<script setup>
中直接使用await
关键字。 这在异步加载数据时非常方便。<template> <div> {{ data }} </div> </template> <script setup> import { ref } from 'vue'; const data = ref(null); const fetchData = async () => { const response = await fetch('/api/data'); data.value = await response.json(); }; // 顶层 await await fetchData(); </script>
-
TypeScript 支持:
<script setup>
和 TypeScript 是天生一对。 使用 TypeScript 可以获得更好的类型检查和代码提示。 强烈建议在<script setup>
中使用 TypeScript。<template> <div> {{ message }} </div> </template> <script setup lang="ts"> import { ref } from 'vue'; const message = ref<string>('Hello, world!'); </script>
-
命名冲突: 要避免在
<script setup>
中声明的变量和函数与模板中的变量和函数发生命名冲突。 如果发生冲突,编译器可能会报错。 -
调试: 由于
<script setup>
是一个语法糖,所以在调试时可能会遇到一些困难。 建议使用 Vue Devtools 来调试<script setup>
组件。
总结:<script setup>
,让你的 Vue 代码更上一层楼
<script setup>
是 Vue 3 中一个非常强大的语法糖,它可以让你的 Composition API 代码更加简洁、易读。 但是,要真正掌握 <script setup>
,你需要了解它背后的原理和注意事项。
希望今天的讲座能帮助你更好地理解 <script setup>
,并在实际开发中灵活运用它,让你的 Vue 代码更上一层楼!
特性 | 描述 |
---|---|
自动暴露 | 在 <script setup> 中声明的顶层变量和函数会自动暴露给模板使用,无需显式 return 。 |
自动注册组件 | 导入的组件会自动注册,无需手动在 components 选项中注册。 |
defineProps |
用于声明组件的 props,支持对象形式和数组形式。 |
defineEmits |
用于声明组件的 emits,支持数组形式和对象形式 (推荐,需要 TypeScript 支持)。 |
defineExpose |
用于显式地暴露组件的变量和函数给父组件访问。 |
useSlots / useAttrs |
用于在组件中使用 slots 和 attrs。 |
顶层 await |
支持在 <script setup> 中直接使用 await 关键字。 |
TypeScript 支持 | 强烈建议在 <script setup> 中使用 TypeScript,以获得更好的类型检查和代码提示。 |
不能和 export default 并存 |
<script setup> 已经隐式地导出了组件,所以不能再使用 export default 。 如果需要导出一些额外的配置,可以使用单独的 <script> 标签。 |
编译过程 | 编译器会将 <script setup> 中的代码转换成标准的 JavaScript 代码,包括包裹在 setup 函数中、处理顶层声明、处理 defineProps 和 defineEmits 、处理 defineExpose 、自动注册组件等等。 |