Vue.js 3 - Composition API使用详解11(<script setup> 语法)
作者:hangge | 2026-05-09 08:34
Vue.js 3 不仅支持普通的 <script> 语法,还支持 <script setup> 语法。该语法的本质是在单文件组件(SFC)中使用 Composition API 的编译时语法糖,方便我们在 script 顶层编写 setup 相关的代码,让代码看起来更简洁,并可以提高开发效率。<script setup> 语法是默认推荐的,相比于普通的 <script> 语法,它具有以下优势:
(3)需要注意的是,任何在 <script setup> 中声明的顶层的绑定内容都能在模板中直接使用。例如,声明的普通变量、响应式变量、函数、import 引入的内容,包含对象、组件、动态组件、 指令等,代码如下所示:
(3)父组件导入 DefinePropsEmitAPI 组件,接着在模板中使用该组件时。为它传递 message 属性,并监听 increment 事件。
(4)运行效果如下,单击“发射 emit 事件”按钮,会调用 emitEvent 函数,控制台输出响应内容。
(3)接着父组件通过如下方式访问 DefineExposeAPI 组件暴露出来的属性和方法:
(4)可以看到浏览器的控制台输出如下图所示,说明父组件组件可以访问到子组件暴露出来的 name、age 和 showMessage。
(3)其实,在 <script setup> 中使用 slots 和 attrs 是很罕见的,因为在模板中可以直接使用 $slots 和 $attrs。
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和抛出事件。
- 更好的运行时性能(其模板会被编译成与其同一作用域的渲染函数,没有任何中间代理)。
- 更好的 IDE 类型推断性能(减少语言服务器从代码中抽离类型的工作)。
十一、<script setup> 语法
1,基本使用
(1)<script setup> 的基本语法如下:
- 在启用该语法时,需要将 setup 属性添加到 <script> 标签上。<script> 标签中的代码会被编译成组件 setup 函数的内容。
- <script> 中的代码只在组件被首次引入时执行一次,而 <script setup> 中的代码会在每次组件实例被创建时执行。
- 任何在 <script setup> 中声明的顶层的绑定(包括变量、函数声明,以及 import 引入的内容)都能在模板中直接使用。
(2)下面是一个使用 <script setup> 语法糖编写计数器的样例,可以看到,该组件使用 <script setup> 语法糖编写,在顶层定义的 counter 变量和 increment 函数能在模板中直接使用。
<template>
<div>
<h4>当前计数: {{ counter }}</h4>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
// ref、counter、increment是在顶层绑定,所以都能在模板中直接使用
import { ref } from 'vue';
const counter = ref(0);
const increment = () => counter.value++;
</script>
(3)需要注意的是,任何在 <script setup> 中声明的顶层的绑定内容都能在模板中直接使用。例如,声明的普通变量、响应式变量、函数、import 引入的内容,包含对象、组件、动态组件、 指令等,代码如下所示:
<template>
<Child />
<component :is="Foo" />
<h4 v-my-directive>This is a Heading</h4>
<div>{{ capitalize('hello') }}</div>
<button @click="count++">{{ count }}</button>
<div @click="log">{{ msg }}</div>
</template>
<!--下面的声明内容都可直接在模板中使用-->
<script setup>
import Child from './Child.vue' //1.声明组件
import Foo from './Foo.vue' //2.声明动态组件
import { myDirective as vMyDirective } from './MyDirective.js'//3.声明指令
import { capitalize } from './helpers'//4.声明工具函数
import { ref } from 'vue'//5.声明ref函数
const count = ref(0) //6.声明响应式变量
const msg = 'Hello!'//7.声明普通变量
function log() {//8.声明函数
console.log(msg)
}
</script>
2,defineProps 和 defineEmits 函数
(1)在 Options API 中,我们可以在 props 选项中定义组件的属性,在 emits 选项中定义触发的事件。而在 <script setup> 语法中,必须用 defineProps 和 defineEmits 函数来声明 props 和 emits. 这两个函数有如下特点:
- defineProps 和 defineEmits 函数都是只在 <script setup> 中才能使用的编译器宏。它们不需要导入且会随着 <script setup> 处理过程一同被编译。
- defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
- 在选项传入后,defineProps 和 defineEmits 会提供恰当的类型推断。
- 传入 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的范围内。因此,传入的选项不能引用在 setup 范围中声明的局部变量,这样做会引起编译错误。但是, 它可以引用导入的绑定,因为导入的绑定也在模块范围内。
(2)为方便演示,我们这里创建一个 DefinePropsEmitAPI.vue 组件,代码如下:
- 首先使用 defineProps 函数为组件定义 message 属性,使用 defineEmits 函数为组件注册 increment 事件,并返回 emit 函数。
- 当单击“发射 emit 事件”按钮时,先打印父组件传递进来的 message,然后使用 emit 函数触发 increment 事件。
<template>
<div style="border:1px solid #ddd;margin:8px">
<div>DefinePropsEmitAPI组件</div>
<p>{{message}}</p>
<button @click="emitEvent">发射emit事件</button>
</div>
</template>
<script setup>
// 1.定义props属性(等同于Options API的props选项)
const props = defineProps({
// message: String,
message: {
type: String,
default: "默认的message"
}
})
// 2.注册需要触发的emit事件
const emit = defineEmits(["increment"]);
// 3.点击 发射emit事件 按钮的回调
const emitEvent = () => {
console.log('子组件拿到父组件传递进来的message: ' + props.message)
emit('increment', 1) // 触发 increment 事件,传递参数:1
}
</script>
(3)父组件导入 DefinePropsEmitAPI 组件,接着在模板中使用该组件时。为它传递 message 属性,并监听 increment 事件。
<template>
<div class="app" style="border:1px solid #ddd;margin:4px">
App组件
<DefinePropsEmitAPI message="App传递过来的message" @increment="getCounter" />
</div>
</template>
<script setup>
import DefinePropsEmitAPI from './DefinePropsEmitAPI.vue';
const getCounter = (number) => {
console.log('App 组件拿到子组件传递过来的number:' + number)
}
</script>
(4)运行效果如下,单击“发射 emit 事件”按钮,会调用 emitEvent 函数,控制台输出响应内容。

3,defineExpose 函数
(1)组件在使用 <script setup> 语法时默认是关闭的,即通过模板 ref 或 Sparent 获取到组件的实例不会暴露任何在 <script setup> 中声明的属性。这时,如果要将组件的某些属性暴露出去,可以通过 defineExpose 编译器宏来实现。
(2)为方便演示,我们这里创建一个 DefineExposeAPI.vue 组件。我们在该组件中定义了 age、name 属性和 showMessage 方法,然后通过 defineExpose API 将它们暴露出去。
<template>
<div style="border:1px solid #ddd;margin:8px">
DefineExposeAPI 组件
</div>
</template>
<script setup>
import { ref } from 'vue'
const age = 28 // 普通数据
const name = ref('hangge') // 响应式数据
const showMessage = () => { console.log('showMessage方法') } // 方法
// 该组件暴露出去的属性( age,name,showMessage )
defineExpose({ age, name, showMessage })
</script>
(3)接着父组件通过如下方式访问 DefineExposeAPI 组件暴露出来的属性和方法:
- 首先,使用 ref 定义 defineExposeAPI 变量,并将其绑定到 DefineExposeAPI 组件的 ref 属性上,以获取该组件的实例。
- 接着,在 watchEffect 函数中获取该组件实例,以及该组件暴露出来的 age、name 属性和 showMessage 方法。
<template>
<div class="app" style="border:1px solid #ddd;margin:4px">
App组件
<DefineExposeAPI ref="defineExposeAPI"></DefineExposeAPI>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
import DefineExposeAPI from './DefineExposeAPI.vue';
// 获取DefineExposeAPI组件的实例和该组件暴露的属性
const defineExposeAPI = ref(null)
watchEffect(() => {
console.log(defineExposeAPI.value) // 组件的实例
console.log(defineExposeAPI.value.name) // 响应式数据
console.log(defineExposeAPI.value.age)
defineExposeAPI.value.showMessage()
}, { flush: "post" })
</script>
(4)可以看到浏览器的控制台输出如下图所示,说明父组件组件可以访问到子组件暴露出来的 name、age 和 showMessage。

4,useSlots 和 useAttrs 函数
(1)setup 函数主要有两个参数;props 和 context。其中,context 包含 slots、attrs 和 emit 三个属性。具体可以查看我之前写的文章:
(2)而在 <script setup> 中,可以从 vue 中导入 useSlots 和 useAttrs 两个辅助函数,然后分别调用这两个函数获取 slots 和 attrs。
注意:useSlots 和 useAttrs 是真实的运行时函数,需要导入后使用。它会返回与 setupContext.slots 和 setupContext.attrs 等价的值,也能在普通的 Composition API 中使用。
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots() //1.获取该组件的插槽,相当于setup函数中的context.slots
const attrs = useAttrs() //2.获取该组件所有的属性,相当于setup函数中的context.attrs
</script>
(3)其实,在 <script setup> 中使用 slots 和 attrs 是很罕见的,因为在模板中可以直接使用 $slots 和 $attrs。
<template>
<!-- 自动将父组件传入的非 props 属性绑定到 div 上 -->
<div class="card" v-bind="$attrs">
<!-- 使用 $slots 判断具名插槽是否存在 -->
<header v-if="$slots.header" class="card-header">
<slot name="header" />
</header>
<main class="card-body">
<slot />
</main>
<footer v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</footer>
</div>
</template>
<script setup>
</script>
全部评论(0)