下面,我们来系统的梳理关于 Vue 3 <script setup>
语法糖 的基本知识点:
一、<script setup>
核心概念
1.1 什么是 <script setup>
?
<script setup>
是 Vue 3 中 Composition API 的编译时语法糖,它通过简化组件声明方式,显著减少样板代码,提供更符合直觉的开发体验。
1.2 设计目标与优势
目标 | 实现方式 | 优势 |
---|---|---|
减少样板代码 | 自动暴露顶层绑定 | 代码更简洁 |
提升开发体验 | 更自然的响应式写法 | 开发更高效 |
更好的类型支持 | 原生 TypeScript 集成 | 类型安全 |
编译时优化 | 编译阶段处理 | 运行时更高效 |
逻辑组织 | 组合式 API 原生支持 | 关注点分离 |
1.3 与传统语法对比
<!-- 选项式 API -->
<script>
export default {
props: ['message'],
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
</script>
<!-- Composition API 标准 -->
<script>
import { ref } from 'vue'
export default {
props: ['message'],
setup(props) {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
}
}
</script>
<!-- <script setup> 语法糖 -->
<script setup>
import { ref } from 'vue'
const props = defineProps(['message'])
const count = ref(0)
function increment() {
count.value++
}
</script>
二、核心语法与特性
2.1 基本结构
<script setup>
// 所有顶层绑定都自动暴露给模板
import { ref } from 'vue'
// 响应式状态
const count = ref(0)
// 函数
function increment() {
count.value++
}
// 表达式
const double = computed(() => count.value * 2)
</script>
<template>
<button @click="increment">
{{ count }} ({{ double }})
</button>
</template>
2.2 编译器转换原理
输入:
<script setup>
const msg = 'Hello!'
</script>
输出:
<script>
export default {
setup() {
const msg = 'Hello!'
return { msg } // 自动暴露所有顶层绑定
}
}
</script>
2.3 自动暴露规则
- 所有
import
导入 - 顶层变量声明 (
const
,let
,var
) - 顶层函数声明
- 顶层
class
和interface
(TypeScript)
三、组件与指令使用
3.1 组件使用
<script setup>
// 1. 导入组件
import MyComponent from './MyComponent.vue'
// 2. 自动注册,无需components选项
</script>
<template>
<!-- 3. 直接在模板中使用 -->
<MyComponent />
</template>
3.2 动态组件
<script setup>
import { shallowRef } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const currentComponent = shallowRef(Home)
</script>
<template>
<component :is="currentComponent" />
</template>
3.3 自定义指令
<script setup>
// 命名规范:vNameOfDirective
const vFocus = {
mounted: el => el.focus()
}
</script>
<template>
<input v-focus />
</template>
四、Props与Emit处理
4.1 声明Props
<script setup>
// 选项式声明
const props = defineProps({
title: String,
count: {
type: Number,
required: true
}
})
// TypeScript接口声明
interface Props {
title?: string
count: number
}
const props = defineProps<Props>()
</script>
4.2 声明Emit事件
<script setup>
// 数组形式
const emit = defineEmits(['update', 'delete'])
// 对象形式(带验证)
const emit = defineEmits({
update: (payload: { id: number }) => {
return !!payload.id // 返回布尔值表示验证结果
}
})
// TypeScript类型声明
const emit = defineEmits<{
(e: 'update', payload: { id: number }): void
(e: 'delete', id: number): void
}>()
4.3 使用示例
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function handleInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input
:value="modelValue"
@input="handleInput"
/>
</template>
五、响应式与生命周期
5.1 响应式状态管理
<script setup>
import { ref, reactive, computed } from 'vue'
// 基本类型
const count = ref(0)
// 对象类型
const state = reactive({
items: [],
loading: false
})
// 计算属性
const total = computed(() => state.items.length)
// 侦听器
watch(count, (newVal, oldVal) => {
console.log(`Count changed: ${oldVal} → ${newVal}`)
})
</script>
5.2 生命周期钩子
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('Component mounted')
fetchData()
})
onUnmounted(() => {
console.log('Component unmounted')
cleanup()
})
</script>
六、高级特性与模式
6.1 顶层await
<script setup>
const user = ref(null)
const posts = ref([])
// 直接使用await - 自动编译为async setup()
const userData = await fetchUser()
user.value = userData
// 结合watchEffect
watchEffect(async () => {
if (user.value) {
posts.value = await fetchPosts(user.value.id)
}
})
</script>
6.2 使用插槽与上下文
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
// 访问默认插槽内容
const defaultSlotContent = slots.default?.()
</script>
6.3 暴露组件实例
<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
// 暴露公共方法
function focusInput() {
inputRef.value.focus()
}
// 显式暴露
defineExpose({
focusInput
})
</script>
<template>
<input ref="inputRef">
</template>
七、TypeScript集成
7.1 类型安全Props
<script setup lang="ts">
interface Props {
title: string
count?: number
items?: string[]
}
const props = defineProps<Props>()
</script>
7.2 泛型组件
<script setup lang="ts" generic="T">
import { ref } from 'vue'
const items = ref<T[]>([])
const selected = ref<T>()
function addItem(item: T) {
items.value.push(item)
}
</script>
7.3 类型标注Ref
<script setup lang="ts">
import { ref } from 'vue'
interface User {
id: number
name: string
}
// 显式类型标注
const user = ref<User | null>(null)
// 初始化时推断
const count = ref(0) // Ref<number>
</script>
八、最佳实践与组织模式
8.1 代码组织建议
<script setup>
// ----------------------------
// 1. 导入部分
// ----------------------------
import { ref, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'
// ----------------------------
// 2. 定义Props/Emits
// ----------------------------
const props = defineProps({ /* ... */ })
const emit = defineEmits({ /* ... */ })
// ----------------------------
// 3. 响应式状态
// ----------------------------
const count = ref(0)
const state = reactive({ /* ... */ })
// ----------------------------
// 4. 计算属性和侦听器
// ----------------------------
const double = computed(() => count.value * 2)
watch(count, (val) => { /* ... */ })
// ----------------------------
// 5. 函数声明
// ----------------------------
function increment() { count.value++ }
// ----------------------------
// 6. 生命周期钩子
// ----------------------------
onMounted(() => { /* ... */ })
// ----------------------------
// 7. 暴露API
// ----------------------------
defineExpose({ increment })
</script>
8.2 逻辑复用模式
<script setup>
// 使用自定义Hook
import { useCounter } from '@/composables/useCounter'
const { count, increment } = useCounter(10)
</script>
8.3 性能优化
<script setup>
// 1. 使用shallowRef减少大型对象的响应式开销
const bigData = shallowRef(largeDataset)
// 2. 使用markRaw避免不必要的响应式转换
const staticConfig = markRaw({ /* 大型配置对象 */ })
// 3. 合理使用computed缓存计算结果
const filteredList = computed(() =>
bigData.value.filter(item => item.active)
)
</script>
九、常见问题与解决方案
9.1 响应式丢失问题
问题:解构Props导致响应式丢失
<script setup>
const { title } = defineProps(['title']) // 失去响应性
</script>
解决方案:
<script setup>
const props = defineProps(['title'])
// 使用toRefs解构
const { title } = toRefs(props)
</script>
9.2 命名冲突问题
问题:导入组件与局部变量同名
<script setup>
import Button from './Button.vue'
const Button = ref(null) // 命名冲突
</script>
解决方案:
<script setup>
import Button as BaseButton from './Button.vue'
const Button = ref(null) // 避免冲突
</script>
9.3 访问上下文问题
问题:无法直接访问 this
<script setup>
// 错误:this未定义
const router = this.$router
</script>
解决方案:
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter() // 使用Composition API
</script>