一、前言:什么是 script setup 语法糖?
<script setup> 是 Vue3.2 版本正式推出的编译层语法糖 ,也是目前 Vue3 企业级项目的标准主流开发写法。
在 Vue3 原始组合式 API 写法中,开发者需要在 setup 函数内定义所有变量、方法与响应式数据,且必须手动 return 暴露后,模板才能正常使用;同时还需手动注册组件、编写模块导出配置,整体代码冗余度高、开发效率低。
而 script setup 语法糖 对组合式 API 进行了全方位简化,剔除了冗余模板代码,核心优势汇总如下:
- 无需定义 setup 函数、无需编写 export default、无需手动 return 暴露数据
- 组件导入即自动注册,无需在 components 配置项中手动声明
- 顶层声明的变量、函数、响应式数据,模板可直接使用
- 自定义指令可自动生效,无需额外配置注册逻辑
- 代码结构更简洁、业务逻辑更集中,可有效减小打包体积、提升页面运行性能
二、基础特性:组件自动注册
在 script setup 语法糖规范中,遵循导入即注册的规则,无需手动配置组件注册信息。组件名称默认以导入的文件名称为准,无需单独为组件配置 name 属性,极大简化了组件引入流程。
完整示例代码
xml
<template>
<!-- 直接使用导入的子组件,无需手动注册 -->
<zi-hello />
</template>
<script setup>
// 仅需导入,自动完成全局/局部注册
import ziHello from './ziHello'
</script>
三、响应式数据定义(ref / reactive / toRefs)
语法糖摒弃了 Vue2 的 data 配置项,支持在顶层直接声明响应式数据,定义后可直接在模板、方法中调用,写法更简洁。Vue3 提供三种核心响应式定义方式,适配不同数据类型场景。
- ref:适配基础数据类型,包含字符串、数字、布尔值,也可兼容简单对象
- reactive:专属引用数据类型,用于定义对象、数组等复杂结构数据
- toRefs:批量解构 reactive 响应式对象,解决直接解构丢失响应式的问题
完整示例代码
xml
<script setup>
// 按需引入Vue3核心响应式API
import { ref, reactive, toRefs } from 'vue'
// ref 定义基础类型响应式数据
const content = ref('content')
// reactive 定义复杂对象类型响应式数据
const data = reactive({
patternVisible: false,
debugVisible: false,
aboutExeVisible: false
})
// toRefs批量解构,保留每一个属性的响应式特性
const { patternVisible, debugVisible, aboutExeVisible } = toRefs(data)
</script>
四、自定义方法使用
相较于 Vue2 的 methods 配置项写法,script setup 无需单独定义方法配置,可直接在顶层声明普通函数或箭头函数,定义完成后模板可直接绑定事件调用,逻辑更集中。
完整示例代码
xml
<template>
<button @click="onClickHelp">系统帮助</button>
</template>
<script setup>
import { reactive } from 'vue'
// 定义响应式数据
const data = reactive({
aboutExeVisible: false,
})
// 顶层自定义方法,模板直接绑定调用
const onClickHelp = () => {
console.log('触发系统帮助弹窗')
data.aboutExeVisible = true
}
</script>
五、watch 监听使用(单值/多值/深度监听)
script setup 中可直接导入 watch 监听 API,支持多样化监听场景,包含单数据监听、多数据联动监听、嵌套对象深度监听,同时支持立即执行、深度监听等自定义配置,适配绝大多数数据监听业务。
完整示例代码
xml
<script setup>
import { ref, reactive, watch } from 'vue'
// 定义各类响应式测试数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
// 1. 多数据联动监听(数组形式)
watch([sum, msg], (newValue, oldValue) => {
console.log('sum或msg数据发生变化', newValue, oldValue)
}, { immediate: true })
// immediate:初始化页面时立即执行一次监听回调
// 2. 嵌套对象深度监听
watch(() => person.job, (newValue, oldValue) => {
console.log('person的job嵌套数据发生变化', newValue, oldValue)
}, { deep: true })
// deep:开启深度监听,可捕获对象深层属性变化
</script>
六、watchEffect 自动监听
watchEffect 是 Vue3 惰性监听方案,无需手动指定监听数据源,会自动回调函数内部依赖的所有响应式数据,依赖数据发生变化时自动触发回调,且组件初始化时会默认执行一次。
完整示例代码
xml
<script setup>
import { ref, watchEffect } from 'vue'
let sum = ref(0)
// 自动收集内部响应式依赖,无需手动配置监听对象
watchEffect(() => {
// 自动识别sum为依赖数据
const x1 = sum.value
console.log('watchEffect 监听回调执行')
})
</script>
七、computed 计算属性(简写/完整写法)
computed 用于基于原有数据派生新数据,具备缓存机制,仅当依赖数据变化时才会重新计算,可有效提升性能。支持两种写法:只读简写写法、可读可写完整写法,适配不同业务场景。
完整示例代码
xml
<script setup>
import { reactive, computed } from 'vue'
let person = reactive({
firstName: '小',
lastName: '叮当'
})
// 1. 简写写法:只读计算属性(项目最常用)
person.fullName = computed(() => {
return person.firstName + '-' + person.lastName
})
// 2. 完整写法:可读可写计算属性
person.fullName = computed({
// 获取计算属性值时触发
get() {
return person.firstName + '-' + person.lastName
},
// 修改计算属性值时触发
set(value) {
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
</script>
八、setup 专属三大编译器宏
defineProps、defineEmits、defineExpose 是 script setup 专属的三大编译器宏函数,无需手动导入,仅在语法糖环境中生效,是 Vue3 组件通信、实例暴露的核心 API。
1. defineProps 子组件接收父组件参数
用于子组件接收父组件传递的 props 数据,支持参数类型校验、默认值设置、非空校验、自定义规则校验,可有效规范组件传参格式,规避参数异常问题。
父组件代码
xml
<template>
<div>
<Hello :name="name" />
</div>
</template>
<script setup>
import Hello from './Hello'
import { ref } from 'vue'
// 父组件定义传参数据
let name = ref('vue3')
</script>
子组件代码
xml
<template>
<div>我是子组件:{{ name }}</div>
</template>
<script setup>
// 编译器宏,无需导入,直接使用
defineProps({
name: {
type: String, // 限定参数数据类型
default: '默认值', // 参数默认值
required: true, // 是否为必传参数
validator: (value) => {
// 自定义参数校验规则
return value >= 0
}
}
})
</script>
重要避坑提示:defineProps 仅支持「运行时对象声明」和「TS 类型声明」两种方式,二者不可同时使用,否则会触发编译报错。
TS 类型版写法(企业级TS项目主流)
在 TypeScript 项目中,推荐基于类型声明定义 props,搭配withDefaults 快速设置默认值,代码更简洁、类型约束更严格,是 Vue3+TS 标准写法。
xml
<template>
<div>我是TS写法子组件:{{ name }},年龄:{{ age }}</div>
</template>
<script setup lang="ts">
import { withDefaults, defineProps } from 'vue'
// 1. 先定义props类型
interface Props {
name: string
age?: number // 非必传参数
}
// 2. withDefaults 绑定类型 + 设置默认值
const props = withDefaults(defineProps<Props>(), {
name: '默认用户名',
age: 18
})
</script>
TS写法关键规则:
- withDefaults 只能搭配TS类型声明props使用,不兼容运行时对象写法
- 必传参数无需设置默认值,可选参数必须配置默认值兜底
- 类型声明天然具备类型校验能力,无需手动配置 type、validator
2. defineEmits 子组件向父组件传值
用于定义子组件自定义事件,通过触发自定义事件携带参数,实现子组件向父组件通信,是 Vue3 父子组件交互的核心方式之一。
子组件代码
xml
<template>
<div>
<p>我是子组件</p>
<button @click="ziupdata">子组件触发事件</button>
</div>
</template>
<script setup>
// 定义自定义事件列表
const emit = defineEmits(['updata'])
const ziupdata = () => {
// 触发自定义事件并传递参数
emit('updata', '我是子组件传递的值')
}
</script>
父组件代码
xml
<template>
<div>
<h3>我是父组件</h3>
<zi-hello @updata="updata" />
</div>
</template>
<script setup>
import ziHello from './ziHello'
// 接收子组件传递的参数
const updata = (data) => {
console.log('接收子组件数据:', data)
}
</script>
TS 类型版写法(精准约束事件参数类型)
在 TS 项目中,可对defineEmits 做类型约束,精准定义自定义事件名称、参数类型,实现代码强类型校验,杜绝传参类型错误,是企业级项目规范写法。
xml
<template>
<div>
<p>TS写法子组件</p>
<button @click="handleSubmit">提交数据并通知父组件</button>
</div>
</template>
<script setup lang="ts">
// 定义自定义事件类型:约束事件名 + 携带参数类型
const emit = defineEmits<{
// 自定义事件:updata,限定传递 string 类型参数
updata: [msg: string]
// 可继续扩展其他自定义事件
// submit: [id: number, name: string]
}>()
const handleSubmit = () => {
// 触发事件并严格按照类型传参
emit('updata', 'TS写法:子组件传递的字符串参数')
}
</script>
TS写法核心优势:
- 强类型约束,严格校验事件名称、传参类型、参数数量
- 编码阶段即可报错提示,规避运行时参数异常问题
- 代码可读性、可维护性更强,适配大型团队项目
TS写法核心优势
- 强类型约束,严格校验事件名称、传参类型、参数数量
- 编码阶段即可报错提示,规避运行时参数异常问题
- 代码可读性、可维护性更强,适配大型团队项目
3. defineExpose 子组件暴露属性/方法
script setup 语法糖有严格的私有机制:组件内所有数据、方法默认不对外暴露,父组件无法通过 ref 获取子组件实例数据与方法。如需外部组件调用,需通过 defineExpose 主动暴露指定内容。下面提供JS 基础写法 和TS 规范写法,全覆盖项目开发场景。
1)JS 基础写法
子组件代码
xml
<template>
<div>我是子组件</div>
</template>
<script setup>
import { ref, reactive, defineExpose } from 'vue'
// 组件内部私有数据(默认无法外部访问)
let ziage = ref(18)
let ziname = reactive({
name: '赵小磊'
})
// 主动暴露数据/方法,供父组件ref调用
defineExpose({
ziage,
ziname
})
</script>
父组件代码
xml
<template>
<div>
<h3 @click="isclick">点击获取子组件数据</h3>
<zi-hello ref="zihello" />
</div>
</template>
<script setup>
import ziHello from './ziHello'
import { ref } from 'vue'
// 绑定组件ref实例
const zihello = ref()
// 通过ref获取子组件暴露的数据
const isclick = () => {
console.log('子组件ref数据:', zihello.value.ziage)
console.log('子组件reactive数据:', zihello.value.ziname.name)
}
</script>
2)TS 规范写法(强类型约束)
在 TS 项目中,可通过类型接口约束暴露内容,精准限定父组件可获取的实例属性与方法,杜绝任意取值、类型模糊等问题,是大型项目标准规范。
子组件 TS 代码
xml
<template>
<div>TS写法子组件暴露实例</div>
</template>
<script setup lang="ts">
import { ref, reactive, defineExpose } from 'vue'
// 定义响应式数据
const ziage = ref(18)
const ziname = reactive({
name: '赵小磊'
})
// 定义子组件内部方法
const getInfo = () => {
return `姓名:${ziname.name},年龄:${ziage.value}`
}
// 定义暴露实例的类型接口
interface ExposeType {
ziage: number
ziname: {
name: string
}
getInfo: () => string
}
// 强类型暴露属性与方法
defineExpose<ExposeType>({
ziage,
ziname,
getInfo
})
</script>
父组件 TS 代码
xml
<template>
<div>
<button @click="getChildInfo">TS获取子组件实例</button>
<zi-hello ref="childRef" />
</div>
</template>
<script setup lang="ts">
import ziHello from './ziHello'
import { ref } from 'vue'
// 精准定义ref实例类型
const childRef = ref<InstanceType<typeof ziHello>>()
const getChildInfo = () => {
if (childRef.value) {
console.log(childRef.value.ziage)
console.log(childRef.value.ziname.name)
console.log(childRef.value.getInfo())
}
}
</script>
TS 写法核心要点
- 通过 Interface 约束对外暴露的属性、方法类型,实现强类型校验
- 父组件使用 InstanceType<typeof 组件> 自动推导子组件完整实例类型
- 未被 defineExpose 列出的属性/方法,外部完全无法访问,安全性更高
- 编码阶段自动提示实例属性,杜绝拼写错误、类型不匹配问题
3. defineExpose 子组件暴露属性/方法
script setup 语法糖有严格的私有机制:组件内所有数据、方法默认不对外暴露,父组件无法通过 ref 获取子组件实例数据与方法。如需外部组件调用,需通过 defineExpose 主动暴露指定内容。
子组件代码
xml
<template>
<div>我是子组件</div>
</template>
<script setup>
import { ref, reactive, defineExpose } from 'vue'
// 组件内部私有数据(默认无法外部访问)
let ziage = ref(18)
let ziname = reactive({
name: '赵小磊'
})
// 主动暴露数据/方法,供父组件ref调用
defineExpose({
ziage,
ziname
})
</script>
父组件代码
xml
<template>
<div>
<h3 @click="isclick">点击获取子组件数据</h3>
<zi-hello ref="zihello" />
</div>
</template>
<script setup>
import ziHello from './ziHello'
import { ref } from 'vue'
// 绑定组件ref实例
const zihello = ref()
// 通过ref获取子组件暴露的数据
const isclick = () => {
console.log('子组件ref数据:', zihello.value.ziage)
console.log('子组件reactive数据:', zihello.value.ziname.name)
}
</script>
九、祖孙组件传值(provide / inject)
provide / inject 是 Vue3 跨层级传值方案,适用于祖孙多层级组件传值。顶层祖先组件通过 provide 提供数据,任意深层子孙组件通过 inject 直接接收,无需逐层 props 传递,简化跨层级通信逻辑。
祖先组件(传值方)
xml
<template>
<AdoutExe />
</template>
<script setup>
import { ref, provide } from 'vue'
import AdoutExe from '@/components/AdoutExeCom'
let name = ref('Jerry')
// 向所有子孙组件提供数据与方法
provide('provideState', {
name,
changeName: () => {
name.value = '小叮当'
}
})
</script>
子孙组件(接收方)
xml
<script setup>
import { inject } from 'vue'
// 注入祖先组件提供的全局数据
const provideState = inject('provideState')
// 调用祖先组件方法修改数据
provideState.changeName()
console.log(provideState.name)
</script>
十、路由API(useRoute / useRouter)
Vue3 组合式 API 摒弃了 Vue2 的 this. <math xmlns="http://www.w3.org/1998/Math/MathML"> r o u t e 、 t h i s . route、this. </math>route、this.router 全局实例,采用独立路由函数 API,在任意组件内可直接调用,用于获取路由信息、实现编程式路由跳转,适配语法糖模块化开发规范。
- useRoute:只读API,用于获取当前路由详情,包含 query、params、路径、路由元信息等
- useRouter:操作API,用于实现路由跳转、路由替换、页面返回等导航操作
完整示例代码
xml
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 初始化路由实例
const route = useRoute()
const router = useRouter()
// 获取路由参数
console.log('query参数:', route.query)
console.log('params参数:', route.params)
// 编程式路由跳转
router.push({
path: '/index'
})
</script>
十一、核心避坑总结(高频易错)
- 实例暴露陷阱:script setup 内变量、方法默认私有,父组件 ref 获取子组件实例必须通过 defineExpose 主动暴露
- Props 声明规范:defineProps 不支持运行时声明与TS类型声明混用,严格遵循单一声明方式
- 响应式解构陷阱:直接解构 reactive 对象会丢失响应式,必须搭配 toRefs 批量解构
- 深度监听规范:监听对象、数组等复杂数据的深层属性,必须手动开启 deep: true 深度监听
- 组件命名规则:自动注册组件默认以文件名为组件名,无需手动配置组件 name 属性
十二、全文核心总结
script setup 语法糖 作为 Vue3 官方标准开发方案,核心价值是简化代码结构、统一开发规范、提升开发效率,整体具备极简、高效、易维护的特性:
- 剔除冗余代码,无需 return 暴露、无需手动注册组件、无需模块导出配置
- 响应式数据、自定义方法顶层声明,模板直接使用,逻辑清晰集中
- 三大编译器宏函数,极简实现父子组件传参、事件通信、实例暴露
- provide/inject 优雅解决多层祖孙组件跨级传参问题
- 搭配组合式路由API,完全适配所有Vue3业务开发场景