大家好,我是老码,今天咱们来聊聊 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、自动注册组件等等。 |