

你是不是还在为 Vue 组件的那些繁琐语法头疼?每次写个组件都要 export default、methods、data 来回折腾,感觉代码总是啰里啰嗦的?
所幸的是,Vue3的**<script setup>语法糖彻底简化了组件开发,让代码更简洁高效。它通过自动暴露顶层绑定、省略setup()函数定义、增强TypeScript支持等特性,使开发者能够用更少的代码实现更多功能。本文详细介绍了其核心优势、基础用法、组件通信方式,并提供了高级技巧和实战案例。从响应式数据定义到组合式函数封装,再到组件暴露方法,<script setup>**都能显著提升开发体验。另外,本篇文章还解答了常见问题并给出最佳实践建议,是Vue3开发者必学的现代语法规范。
一、什么是 <script setup>
简单来说,**<script setup>**是 Vue3 引入的一种编译时语法糖,它能让单文件组件的脚本部分变得更加简洁明了。
以前我们写个组件得这样:
javascript
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
现在用**<script setup>** 就简单多了:
javascript
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
</script>
看出来了吧?代码一下子清爽了很多!不再需要那些模板化的结构,直接写逻辑就行。
二、为什么要用 <script setup>
你可能要问,我已经习惯原来的写法了,为什么要换呢?这里给你几个无法拒绝的理由:
代码量大幅减少,不用再写那些重复的样板代码。组件间的数据传递和事件处理变得更加直观。更好的 TypeScript 支持,类型推断更加准确。编译时优化,性能更优秀。
最重要的是,写起来真的很快乐!你再也不用在 methods、data、computed 之间来回切换了。
三、基础用法速成
让我们从最简单的开始,一步步掌握 **<script setup>**的核心用法。
定义响应式数据,在 <script setup> 里,我们直接用 ref 和 reactive:
javascript
<script setup>
import { ref, reactive } from 'vue'
// 基本类型用 ref
const name = ref('张三')
const age = ref(25)
// 对象类型可以用 reactive
const userInfo = reactive({
job: '前端开发',
salary: 20000
})
// 修改数据也很简单
const updateInfo = () => {
name.value = '李四' // ref 需要通过 .value 访问
userInfo.salary = 25000 // reactive 直接修改属性
}
</script>
定义方法就更简单了,直接写函数就行:
javascript
<script setup>
const sayHello = () => {
console.log('你好,Vue3!')
}
const calculate = (a, b) => {
return a + b
}
</script>
计算属性也用起来:
javascript
<script setup>
import { ref, computed } from 'vue'
const price = ref(100)
const quantity = ref(2)
// 计算总价
const total = computed(() => {
return price.value * quantity.value
})
// 复杂的计算属性
const discountTotal = computed(() => {
const totalVal = price.value * quantity.value
return totalVal > 200 ? totalVal * 0.9 : totalVal
})
</script>
四、组件通信变得超简单
在**<script setup>**里,组件间的通信也变得特别直观。
定义 props 可以用 defineProps:
javascript
<script setup>
// 基础用法
defineProps(['title', 'content'])
// 带类型检查的用法
defineProps({
title: String,
content: {
type: String,
required: true
},
count: {
type: Number,
default: 0
}
})
// 用 TypeScript 的话更简单
defineProps<{
title?: string
content: string
count?: number
}>()
</script>
定义 emits 用 defineEmits:
javascript
<script setup>
// 基础用法
const emit = defineEmits(['update', 'delete'])
// 带验证的用法
const emit = defineEmits({
update: (id) => {
if (id) return true
console.warn('需要提供 id')
return false
}
})
// 实际使用
const handleUpdate = () => {
emit('update', 123)
}
const handleDelete = () => {
emit('delete', 456)
}
</script>
五、高级技巧让你更专业
掌握了基础用法,再来看看一些提升效率的高级技巧。
✔ 使用组合式函数,这是 Vue3 的精髓之一:
javascript
<script setup>
// 导入 Vue 的响应式 API 和生命周期钩子
import { ref, onMounted } from 'vue'
// 封装一个获取数据的组合式函数,即定义一个组合式函数 useFetch,参数为请求的 URL
const useFetch = (url) => {
// 创建响应式变量 data,初始为 null,用于存储请求成功返回的数据
const data = ref(null)
// 创建响应式变量 loading,初始为 false,表示是否正在请求中
const loading = ref(false)
// 创建响应式变量 error,初始为 null,用于存储请求失败时的错误对象
const error = ref(null)
// 定义异步请求函数 fetchData
const fetchData = async () => {
// 开始请求,将 loading 设为 true
loading.value = true
try {
// 发起 fetch 请求,等待响应
const response = await fetch(url)
// 将响应体解析为 JSON,并存入 data.value
data.value = await response.json()
} catch (err) {
// 如果请求或解析过程中抛出异常,将错误对象存入 error.value
error.value = err
} finally {
// 无论成功或失败,最终将 loading 置为 false
loading.value = false
}
}
// 组件挂载完成后自动执行第一次数据获取
onMounted(fetchData)
// 返回响应式数据和控制函数,供组件使用
return {
data,
loading,
error,
refetch: fetchData // 允许手动重新请求
}
}
// 在组件中调用 useFetch,解构出 userData、loading、error
// 注意:这里将 data 重命名为 userData
const { data: userData, loading, error } = useFetch('/api/user')
</script>
● 注:fetchData函数为什么不用加括号()
onMounted(fetchData) 中的 fetchData不需要手动调用。
onMounted 是 Vue 的生命周期钩子,它接收一个函数作为参数,并在组件挂载完成后自动执行这个函数。
因此,这里的 fetchData 只是把函数引用传给了 onMounted,由 Vue 在合适的时机调用它,而不是在定义时立即执行。
在 `onMounted(fetchData)` 中,`fetchData` 不加括号是因为我们需要传递**函数本身**作为回调,而不是立即执行它。
☑ 加括号 ------onMounted(fetchData()):会立即调用 `fetchData`,然后将它的返回值(一个 `Promise`)传给 `onMounted`。这会导致请求在组件挂载之前就发起,而且 `onMounted` 收到的是一个 `Promise` 而不是函数,无法在挂载后再次执行。
☑ 不加括号 ------onMounted(fetchData):只是将 fetchData`这个函数的引用传给 onMounted。Vue 内部会在组件挂载完成后,自动调用这个函数,从而实现期望的"挂载后请求数据"的逻辑。
简单类比:就像 `setTimeout(yourFunction, 1000)` 要传函数名,而不是 `yourFunction()`。
✔ 使用 defineExpose 暴露组件方法:
javascript
<script setup>
// 从 Vue 中导入 ref 函数,用于创建响应式数据
import { ref } from 'vue'
// 创建一个响应式引用 count,初始值为 0
// count.value 可以读取或修改,变化时会触发视图更新
const count = ref(0)
// 定义 increment 函数:将 count 的值增加 1
const increment = () => {
count.value++ // 直接修改响应式数据的 value 属性
}
// 定义 reset 函数:将 count 的值重置为 0
const reset = () => {
count.value = 0
}
// 使用 defineExpose 显式暴露 increment 和 reset 方法给父组件
// 父组件可以通过模板 ref 调用这些方法(例如:childRef.increment())
defineExpose({
increment,
reset
})
</script>
✔ 使用 useSlots 和 useAttrs:
javascript
<script setup>
// 从 Vue 中导入 useSlots 和 useAttrs 组合式 API
import { useSlots, useAttrs } from 'vue'
// 调用 useSlots() 获取当前组件的插槽对象
// 该对象包含了父组件传递的所有具名插槽和默认插槽
const slots = useSlots()
// 调用 useAttrs() 获取当前组件接收到的所有属性(非 props 声明的属性)
// 例如 class、style、id 以及自定义属性等
const attrs = useAttrs()
// 判断 slots 对象中是否存在名为 'header' 的插槽
// !! 将 slots.header 转换为布尔值:存在则为 true,不存在则为 false
// 可以用于条件渲染插槽内容或控制样式
const hasHeaderSlot = !!slots.header
// 从 attrs 对象中解构取出 class 属性值(如果不存在则默认为空字符串)
// 可以用于动态合并或处理外部传入的 CSS 类名
const extraClass = attrs.class || ''
</script>
●注:const hasHeaderSlot = !!slots.header
这是JavaScript中常见的将任意值转换为布尔值的技巧。!是逻辑非运算符,两次!!就是将值强制转换为布尔类型。slots.header可能为undefined或一个函数(插槽存在时)。使用!!可以确保hasHeaderSlot是一个布尔值(true/false),而不是其他truthy/falsy值。所以:将slots.header转换为布尔值,方便后续条件判断。
在 JavaScript 中,!! 是一种将任意值强制转换为布尔类型(true 或 false)的常用写法。
slots.header 的值可能是 undefined(没有该插槽)或一个函数(存在该插槽)。
- 单个 ! 会先取反:!slots.header 将 undefined 转为 true,将函数对象转为 false。
- 再取反一次 !!,就得到原始值的布尔等价形式:有插槽时 true,无插槽时 false。
这样 hasHeaderSlot 就是一个明确的布尔值,可以直接用于 v-if 或条件渲染,代码更清晰、严谨。
与 Boolean() 的关系
!!value 与 Boolean(value) 效果完全相同,区别仅在于写法:
- !!value:更简洁,常见于代码 golf 或追求简洁的场景;
- Boolean(value):更具可读性,适合团队协作项目。
javascript
!!42; // true
Boolean(42); // true
**注意事项:**在条件语句(如 if、while)中,JavaScript 会自动进行隐式布尔转换,通常不需要使用 !!:
javascript
const value = "hello";
if (value) { // 自动转换,无需 !!value
console.log("有值");
}
只有在需要显式获得布尔值 (如赋值、返回、传参)时,才推荐使用 !!。
六、实战案例:打造一个任务管理器
我们来写个完整的任务管理组件:
html
<template>
<!-- 任务管理器主容器 -->
<div class="task-manager">
<!-- 添加任务区域 -->
<div class="add-task">
<!-- 输入框:双向绑定 newTask,按回车触发 addTask -->
<input
v-model="newTask"
@keyup.enter="addTask"
placeholder="输入新任务..."
class="task-input"
>
<!-- 添加按钮:点击触发 addTask -->
<button @click="addTask" class="add-btn">添加</button>
</div>
<!-- 任务列表区域 -->
<div class="task-list">
<!-- 遍历 filteredTasks(根据筛选条件计算出的任务列表) -->
<div
v-for="task in filteredTasks"
:key="task.id"
:class="['task-item', { completed: task.completed }]"
>
<!-- 复选框:双向绑定任务的 completed 状态 -->
<input
type="checkbox"
v-model="task.completed"
class="task-checkbox"
>
<!-- 任务文本,当 completed 为 true 时应用删除线样式 -->
<span class="task-text">{{ task.text }}</span>
<!-- 删除按钮:点击时传入任务 id 调用 removeTask -->
<button @click="removeTask(task.id)" class="remove-btn">删除</button>
</div>
</div>
<!-- 任务统计与筛选区域 -->
<div class="task-stats">
<span>总计: {{ totalTasks }} 个任务</span>
<span>已完成: {{ completedTasks }} 个</span>
<!-- 点击按钮修改 filter 的值,触发 filteredTasks 重新计算 -->
<button @click="filter = 'all'">全部</button>
<button @click="filter = 'active'">未完成</button>
<button @click="filter = 'completed'">已完成</button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
// 响应式数据
const newTask = ref('') // 绑定输入框的新任务文本
const tasks = ref([]) // 存储所有任务的数组,每个任务包含 id、text、completed
const filter = ref('all') // 当前筛选条件:'all' | 'active' | 'completed'
// 添加新任务
const addTask = () => {
// 去除首尾空格后判断是否为空
if (newTask.value.trim()) {
// 向 tasks 数组推入新任务对象(id 使用时间戳,text 为去除空格的输入值,completed 默认为 false)
tasks.value.push({
id: Date.now(),
text: newTask.value.trim(),
completed: false
})
newTask.value = '' // 清空输入框
saveToLocalStorage() // 保存到 localStorage
}
}
// 删除任务
const removeTask = (id) => {
// 通过 filter 过滤掉匹配 id 的任务,重新赋值给 tasks
tasks.value = tasks.value.filter(task => task.id !== id)
saveToLocalStorage() // 更新本地存储
}
// 计算属性:任务总数
const totalTasks = computed(() => tasks.value.length)
// 计算属性:已完成任务数量
const completedTasks = computed(() =>
tasks.value.filter(task => task.completed).length
)
// 计算属性:根据 filter 筛选后的任务列表
const filteredTasks = computed(() => {
switch (filter.value) {
case 'active': // 未完成:completed === false
return tasks.value.filter(task => !task.completed)
case 'completed': // 已完成:completed === true
return tasks.value.filter(task => task.completed)
default: // 全部
return tasks.value
}
})
// 本地存储:将 tasks 数组保存到浏览器的 localStorage 中
const saveToLocalStorage = () => {
localStorage.setItem('vue-tasks', JSON.stringify(tasks.value))
}
// 从 localStorage 加载保存的任务数据
const loadFromLocalStorage = () => {
const saved = localStorage.getItem('vue-tasks')
if (saved) {
tasks.value = JSON.parse(saved) // 解析后恢复 tasks 数组
}
}
// 生命周期:组件挂载后自动加载本地存储中的任务
onMounted(() => {
loadFromLocalStorage()
})
</script>
<style scoped>
/* 样式部分略,仅作用于当前组件,实现任务管理器的视觉布局 */
.task-manager {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.task-input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 8px;
}
.add-btn {
padding: 8px 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.task-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
}
.task-item.completed .task-text {
text-decoration: line-through;
color: #888;
}
.task-checkbox {
margin-right: 8px;
}
.task-text {
flex: 1;
}
.remove-btn {
padding: 4px 8px;
background: #ff4757;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.task-stats {
margin-top: 16px;
display: flex;
gap: 12px;
align-items: center;
}
</style>
这个例子展示了 **<script setup>**在实际项目中的强大能力,代码结构清晰,逻辑组织得当。
七、常见问题解答
Q: 从 Options API 迁移到 <script setup> 难吗? A: 其实不难!大部分概念都是相通的,只是写法更简洁了。建议先从简单的组件开始尝试。
Q: <script setup> 对 TypeScript 支持怎么样? A: 支持非常好!类型推断更加准确,写起来特别舒服。
Q: 还能和普通的 <script> 混用吗? A: 可以的,但通常不建议。除非你有特殊的模块导出需求。
Q: 现有的 Vue2 项目能直接用吗? A: 需要升级到 Vue3,但升级过程比想象中简单,官方提供了详细的迁移指南。
八、最佳实践推荐
按逻辑组织代码,而不是按功能类型。把相关的数据、方法、计算属性放在一起。合理使用组合式函数抽离可复用逻辑。使用 TypeScript 获得更好的开发体验。保持组件的单一职责,不要写太复杂的组件。
九、<script setup> 总结
<script setup> 是 Vue 3 中引入的一种编译时语法糖,用于在单文件组件(SFC)中更简洁地使用组合式 API(Composition API)。它显著简化了组件的书写方式,提升了开发体验和运行性能。
☑ 核心作用
- 自动暴露顶层绑定 :在
<script setup>中声明的变量、函数、导入的内容,无需手动 return,即可直接在模板中使用 。 - 省略
setup()函数定义 :无需再写export default { setup() { ... } }的样板代码,逻辑更紧凑。 - 自动注册组件与指令 :
- 导入的子组件可直接在模板中作为标签使用,无需在
components中声明。 - 本地自定义指令只需符合命名规范(如
vMyDirective),即可直接在模板中使用。
- 导入的子组件可直接在模板中作为标签使用,无需在
- 增强 TypeScript 支持 :
- 使用
defineProps和defineEmits可获得完整的类型推断,支持纯类型声明。 - 在 Vue 3.5+ 中,从
defineProps解构出的变量默认是响应式的。
- 使用
- 优化运行时性能 :
- 模板被编译为与
<script setup>同一作用域的渲染函数,避免中间代理对象,减少运行时代价。
- 模板被编译为与
- 支持编译器宏 :
defineProps、defineEmits、defineExpose、defineModel(Vue 3.4+)等宏无需导入,仅参与编译,不产生运行时开销。
- 支持顶层
await:可直接在<script setup>中使用await,代码会被编译为异步 setup 函数。
☑ 典型使用示例
javascript
<template>
<!-- 按钮:点击时触发 increment 方法,显示响应式数据 count 的值 -->
<button @click="increment">{{ count }}</button>
<!-- 自动注册,无需声明 -->
<!-- 使用子组件 ChildComponent,由于 <script setup> 会自动注册导入的组件,无需在 components 选项中声明 -->
<ChildComponent />
</template>
<script setup>
// 从 Vue 中导入 ref 函数,用于创建响应式数据
import { ref } from 'vue'
// 导入子组件(文件路径假设为 ./ChildComponent.vue)
import ChildComponent from './ChildComponent.vue'
// 创建一个响应式引用 count,初始值为 0
const count = ref(0)
// 定义 increment 函数:每次调用使 count 的值增加 1
const increment = () => { count.value++ }
// 声明 props(带类型推断)
// 使用 defineProps 声明组件接收的 props(此处使用 TypeScript 语法进行类型推断)
// 父组件需要传入一个名为 msg 的字符串属性
const props = defineProps<{ msg: string }>()
// 声明 emits
// 使用 defineEmits 声明组件可触发的自定义事件(此处声明一个名为 'update' 的事件,无参数)
const emit = defineEmits<{ (e: 'update'): void }>()
// 暴露方法给父组件(默认不暴露,需显式声明)
// 使用 defineExpose 显式暴露 increment 方法给父组件
// 父组件可通过模板 ref 访问该组件实例,并调用 increment 方法
// 注意:<script setup> 中的变量默认不暴露,需要显式声明才能被父组件访问
defineExpose({ increment })
</script>
☑ 注意事项
<script setup>中的代码在每次组件实例创建时执行 ,而普通<script>仅在模块首次导入时执行一次。- 若需使用传统选项(如
inheritAttrs),需配合普通<script>块。 - 默认情况下,组件内部变量不会暴露给父组件 ,如需暴露,必须使用
defineExpose。
☑ 推荐场景
- 新项目开发,追求**代码简洁与开发效率。**
- 复杂逻辑组件,利用组合式 API 集中管理状态。
- TypeScript 项目,依赖强类型校验。
- 组件库开发,降低使用者学习成本。
官方文档推荐:在使用 SFC + Composition API 时,**优先使用
<script setup>**
✔ 参考资料 ✔
官方介绍组合式 API:setup() | 官方介绍<script setup>
Vue3中的script setup语法糖的使用及说明 | Vue3 script setup 语法糖(升级版)
vue3入门- script setup详解上-CSDN | vue3入门- script setup详解下-CSDN
vue3 语法糖<script setup>| UniApp中App.vue使用Vue3的<script setup>写法详解
