Vue3的
<script setup>语法糖简化了Composition API的使用,主要特点包括:
- 自动暴露顶级变量给模板
- 组件自动注册
- 使用
defineProps/defineEmits声明属性和事件- 通过
defineExpose暴露方法
它显著减少了样板代码,支持TypeScript类型推断和顶层await,使组件开发更简洁高效。
相比传统Options API,
<script setup>代码量减少约30%,更适合现代JavaScript开发模式,是Vue3项目的推荐写法,尤其适合需要良好类型支持和追求开发效率的场景。
<script setup>
<script setup> 是 Vue 3 中编写单文件组件(SFC)的编译时语法糖,它让 Composition API 的代码更加简洁、直观。
这是 Vue 3 最重要的开发体验改进之一。
基本语法
vue
javascript
<!-- 传统 Options API -->
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
<!-- 使用 <script setup> -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
可以看到,代码量显著减少,更符合 JavaScript 的自然写法。
核心特性
1. 顶级变量自动暴露给模板
在 <script setup> 中声明的所有顶级变量 (包括导入的)都会自动暴露给模板,无需 return。
vue
javascript
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
// 自动暴露给模板
const count = ref(0)
const message = 'Hello Vue 3!'
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<!-- 所有顶级变量都可以直接使用 -->
<MyComponent />
<h1>{{ message }}</h1>
<p>{{ count }} x 2 = {{ doubleCount }}</p>
<button @click="increment">+1</button>
</template>
2. 组件自动注册
导入的组件无需在 components 选项中注册,可以直接在模板中使用。
vue
javascript
<script setup>
// 导入后直接可用
import Button from './Button.vue'
import Modal from './Modal.vue'
import Icon from './Icon.vue'
</script>
<template>
<Button>点击</Button>
<Modal />
<Icon name="user" />
</template>
3. 定义 Props 和 Emits
使用 defineProps 和 defineEmits 编译器宏来声明:
vue
javascript
<script setup>
// 定义 Props
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 定义 Emits(两种方式)
// 方式1:数组形式
const emit = defineEmits(['update:title', 'submit'])
// 方式2:对象形式(可添加验证)
const emit = defineEmits({
'update:title': (value) => {
return typeof value === 'string'
},
'submit': null // 无需验证
})
// 触发事件
const updateTitle = () => {
emit('update:title', '新标题')
}
</script>
<template>
<h1>{{ title }}</h1>
<p>计数: {{ count }}</p>
</template>
4. 使用 defineExpose 暴露组件方法
默认情况下,<script setup> 中的变量是私有的 。如果需要父组件访问,需要使用 defineExpose:
javascript
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const reset = () => {
count.value = 0
}
// 只暴露 reset 方法,count 保持私有
defineExpose({
reset
})
</script>
<!-- Parent.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
onMounted(() => {
// 只能访问暴露的方法
childRef.value?.reset() // ✅ 可以调用
console.log(childRef.value?.count) // ❌ undefined(私有)
})
</script>
<template>
<Child ref="childRef" />
</template>
5. 使用 useSlots 和 useAttrs
javascript
<script setup>
import { useSlots, useAttrs } from 'vue'
// 访问插槽
const slots = useSlots()
// 访问非 props 的属性(class, style, 自定义属性等)
const attrs = useAttrs()
console.log(slots.default) // 默认插槽内容
console.log(attrs.class) // 传递的 class
</script>
高级用法
1. 与 TypeScript 完美集成
javascript
<script setup lang="ts">
// 使用类型声明 Props 和 Emits
interface Props {
title: string
count?: number
}
interface Emits {
(e: 'update:title', value: string): void
(e: 'submit'): void
}
// 带类型的 Props
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// 带类型的 Emits
const emit = defineEmits<Emits>()
// 自动类型推断
const doubleCount = computed(() => (props.count || 0) * 2)
</script>
2. 顶层 await
<script setup> 支持顶层 await,结果会自动编译成 async setup()。
vue
javascript
<script setup>
// 直接使用 await,无需包装
const user = await fetchUser()
const posts = await fetchPosts()
// 甚至可以在模板中使用
const data = await fetchData()
</script>
<template>
<div v-if="data">
{{ data }}
</div>
</template>
3. 动态组件
vue
javascript
<script setup>
import { shallowRef } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import Contact from './Contact.vue'
const components = {
Home,
About,
Contact
}
const currentTab = shallowRef('Home')
const currentComponent = computed(() => components[currentTab.value])
</script>
<template>
<button
v-for="tab in Object.keys(components)"
:key="tab"
@click="currentTab = tab"
>
{{ tab }}
</button>
<component :is="currentComponent" />
</template>
4. 组合式函数的使用
javascript
<script setup>
import { useMouse, useLocalStorage } from './composables'
// 直接使用组合式函数
const { x, y } = useMouse()
const { value: theme } = useLocalStorage('theme', 'light')
// 组合多个功能
const {
data,
loading,
error,
fetchData
} = useFetch('/api/data')
</script>
与传统 <script> 的对比
| 特性 | <script setup> |
传统 <script> |
|---|---|---|
| 代码量 | 更简洁,减少 ~30% | 需要更多的样板代码 |
| 组件注册 | 自动注册,无需 components 选项 |
需要在 components 中注册 |
| 暴露变量 | 自动暴露顶级变量 | 需要从 setup() 中返回 |
| Props/Emits | 使用 defineProps/defineEmits 编译器宏 |
使用 props 和 emits 选项 |
| TypeScript | 更好的类型推断和集成 | 类型支持有限 |
| 顶层 await | 支持 | 不支持 |
| 学习曲线 | 对 React Hooks 用户更友好 | 对 Vue 2 用户更熟悉 |
常见问题和解决方案
问题1:需要访问组件实例?
vue
javascript
<script setup>
import { getCurrentInstance } from 'vue'
// 获取当前组件实例(谨慎使用)
const instance = getCurrentInstance()
// 访问全局属性
console.log(instance.appContext.config.globalProperties.$router)
// 尽量使用 Composition API 替代
// 例如使用 useRouter() 而不是 this.$router
</script>
问题2:与 Options API 混用?
vue
javascript
<!-- 可以同时使用,但不推荐 -->
<script>
export default {
inheritAttrs: false
}
</script>
<script setup>
// Composition API 代码
</script>
问题3:Props 的响应式解构
vue
javascript
<script setup>
import { toRefs } from 'vue'
const props = defineProps({
user: Object,
active: Boolean
})
// ❌ 错误:直接解构会丢失响应式
const { user, active } = props
// ✅ 正确:使用 toRefs 保持响应式
const { user, active } = toRefs(props)
// 或者使用 computed
const userName = computed(() => props.user?.name)
</script>
最佳实践
1. 统一使用 <script setup>
在新项目中,默认使用 <script setup>,保持代码风格统一。
2. 合理组织代码顺序
javascript
<script setup>
// 1. 导入依赖
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
// 2. 组件导入
import Button from './Button.vue'
import Modal from './Modal.vue'
// 3. Props/Emits 定义
const props = defineProps(/* ... */)
const emit = defineEmits(/* ... */)
// 4. 组合式函数调用
const router = useRouter()
const { data, fetch } = useFetch()
// 5. 响应式状态
const count = ref(0)
const form = reactive({/* ... */})
// 6. 计算属性
const doubleCount = computed(() => count.value * 2)
// 7. 方法函数
const handleSubmit = async () => {
// ...
}
// 8. 生命周期/侦听器
onMounted(() => {
fetch()
})
// 9. 暴露给父组件的方法
defineExpose({
submit: handleSubmit
})
</script>
3. 为复杂组件使用组合式函数
当组件逻辑复杂时,将其拆分为组合式函数:
javascript
<script setup>
// 将复杂逻辑抽离
import { useUserManagement } from './composables/useUserManagement'
import { useFormValidation } from './composables/useFormValidation'
const {
users,
loading,
error,
fetchUsers,
deleteUser
} = useUserManagement()
const {
form,
errors,
validate,
resetForm
} = useFormValidation()
</script>
总结
<script setup> 是 Vue 3 的重大改进,它:
主要优势:
-
✅ 代码更简洁:减少样板代码,提高开发效率
-
✅ 类型支持更好:与 TypeScript 完美集成
-
✅ 自动暴露:无需手动返回模板使用的变量
-
✅ 组件自动注册:导入即用
-
✅ 更符合现代 JS 习惯:像写普通 JavaScript 一样写 Vue
需要注意:
-
学习新的 API:
defineProps、defineEmits、defineExpose -
需要了解响应式保持(使用
toRefs) -
部分 Options API 概念需要转换思维
适用场景:
-
所有新的 Vue 3 项目
-
使用 Composition API 的组件
-
需要良好 TypeScript 支持的项目
-
希望减少样板代码,提高开发效率
<script setup> 代表了 Vue 组件编写的未来方向,是 Vue 3 开发的首选方式。虽然一开始需要适应新的语法,但一旦习惯,你会发现自己再也回不去传统的写法了!