你是不是还在为 Vue 组件的那些繁琐语法头疼?每次写个组件都要 export default、methods、data 来回折腾,感觉代码总是啰里啰嗦的?
告诉你个好消息,Vue3 的 <script setup> 语法糖简直就是为我们这些追求效率的开发者量身定做的!它能让你用更少的代码做更多的事,而且写起来那叫一个爽快。
今天我就带你彻底搞懂这个功能,从基本用法到高级技巧,保证让你看完就能用上,代码量直接减半!
什么是 <script setup>?
简单来说,<script setup> 是 Vue3 引入的一种编译时语法糖,它能让单文件组件的脚本部分变得更加简洁明了。
以前我们写个组件得这样:
vue
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
现在用 <script setup> 就简单多了:
vue
<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:
vue
<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>
定义方法就更简单了,直接写函数就行:
vue
<script setup>
const sayHello = () => {
console.log('你好,Vue3!')
}
const calculate = (a, b) => {
return a + b
}
</script>
计算属性也用起来:
vue
<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:
vue
<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:
vue
<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 的精髓之一:
vue
<script setup>
import { ref, onMounted } from 'vue'
// 封装一个获取数据的组合式函数
const useFetch = (url) => {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
onMounted(fetchData)
return {
data,
loading,
error,
refetch: fetchData
}
}
// 在组件中使用
const { data: userData, loading, error } = useFetch('/api/user')
</script>
使用 defineExpose 暴露组件方法:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++
}
const reset = () => {
count.value = 0
}
// 暴露给父组件的方法
defineExpose({
increment,
reset
})
</script>
使用 useSlots 和 useAttrs:
vue
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
// 可以动态处理插槽和属性
const hasHeaderSlot = !!slots.header
const extraClass = attrs.class || ''
</script>
实战案例:打造一个任务管理器
光说不练假把式,我们来写个完整的任务管理组件:
vue
<template>
<div class="task-manager">
<div class="add-task">
<input
v-model="newTask"
@keyup.enter="addTask"
placeholder="输入新任务..."
class="task-input"
>
<button @click="addTask" class="add-btn">添加</button>
</div>
<div class="task-list">
<div
v-for="task in filteredTasks"
:key="task.id"
:class="['task-item', { completed: task.completed }]"
>
<input
type="checkbox"
v-model="task.completed"
class="task-checkbox"
>
<span class="task-text">{{ task.text }}</span>
<button @click="removeTask(task.id)" class="remove-btn">删除</button>
</div>
</div>
<div class="task-stats">
<span>总计: {{ totalTasks }} 个任务</span>
<span>已完成: {{ completedTasks }} 个</span>
<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([])
const filter = ref('all')
// 添加新任务
const addTask = () => {
if (newTask.value.trim()) {
tasks.value.push({
id: Date.now(),
text: newTask.value.trim(),
completed: false
})
newTask.value = ''
saveToLocalStorage()
}
}
// 删除任务
const removeTask = (id) => {
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
)
const filteredTasks = computed(() => {
switch (filter.value) {
case 'active':
return tasks.value.filter(task => !task.completed)
case 'completed':
return tasks.value.filter(task => task.completed)
default:
return tasks.value
}
})
// 本地存储
const saveToLocalStorage = () => {
localStorage.setItem('vue-tasks', JSON.stringify(tasks.value))
}
const loadFromLocalStorage = () => {
const saved = localStorage.getItem('vue-tasks')
if (saved) {
tasks.value = JSON.parse(saved)
}
}
// 生命周期
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 获得更好的开发体验。保持组件的单一职责,不要写太复杂的组件。