本文是 Vue3 组合式 API 系列的进阶篇聚焦script setup语法糖的全部用法、实战场景、避坑技巧从基础入门到高级实战手把手教你吃透setup语法糖大幅提升 Vue3 开发效率。前置要求掌握 Vue3 组合式 API 基础ref、reactive、computed等了解 Vue2 选项式 API 与 Vue3 组合式 API 的区别具备基础 Vue3 项目开发能力。一、前言为什么要使用在 Vue3 推出组合式 API 初期我们使用setup()函数作为组合式逻辑的入口虽然解决了选项式 API 逻辑碎片化的问题比如一个业务逻辑的代码分散在data、methods、computed中但在实际开发中存在三个明显痛点严重影响开发效率手动暴露冗余所有需要在模板中使用的变量、方法都必须手动通过return暴露代码冗余且容易遗漏一旦遗漏就会导致模板渲染失败排查起来耗时费力组件注册繁琐组件导入后必须在components选项中手动注册如components: { XXX }哪怕是常用的基础组件也需要重复注册增加不必要的代码量TS 适配不佳与 TypeScript 结合时类型声明繁琐需要手动定义接口、标注类型开发体验不够流畅难以发挥 TS 的类型校验优势。为了解决这些问题Vue3.2 版本正式推出script setup语法糖它并非新增功能而是setup()函数的“语法糖简化版”——保留了组合式 API 的所有功能同时大幅简化代码、提升开发效率目前已成为 Vue3 开发的主流写法几乎所有 Vue3 项目包括 Vue3 TS 项目都会优先使用。先看一组直观对比快速感受语法糖的优势1.1 传统 setup() 函数写法template div p{{ count }}/p button clickincrement1/button HelloWorld / /div /template script import { ref } from vue import HelloWorld from ./HelloWorld.vue export default { // 手动注册组件哪怕只导入一个也需要写 components: { HelloWorld }, // 手动定义 setup 函数作为组合式逻辑入口 setup() { // 定义响应式变量 const count ref(0) // 定义方法 const increment () count.value // 手动 return 暴露模板才能使用少写一个就报错 return { count, increment } } } /script1.2script setup语法糖写法template div p{{ count }}/p button clickincrement1/button HelloWorld / /div /template !-- 只需在 script 标签上添加 setup 关键字无需其他多余配置 -- script setup import { ref } from vue // 导入组件自动注册无需手动写 components 选项 import HelloWorld from ./HelloWorld.vue // 顶层声明的变量、方法自动暴露给模板无需 return const count ref(0) const increment () count.value /script对比可见语法糖写法删除了冗余的export default、components注册、return暴露代码量减少近一半且逻辑更清晰——所有组合式逻辑都能在顶层直接编写无需嵌套在 setup 函数中。接下来我们从 0 到 1 实战掌握它的所有用法覆盖基础、进阶、实战、避坑全场景。二、基础入门script setup核心基础用法这一部分是语法糖的基础也是日常开发中最常用的内容必须熟练掌握。核心原则script setup内部的代码本质上就是setup()函数的函数体所有规则与setup()函数一致只是简化了写法。2.1 基本使用语法格式与自动暴露script setup的使用非常简单只需在script标签上添加setup关键字即可无需额外配置核心规则如下重点记顶层声明的变量、函数、类会自动暴露给模板使用无需手动return注意仅顶层声明有效嵌套在函数内部的变量/方法不会自动暴露。语法糖内部的代码相当于在setup()函数内部执行this指向undefined刻意设计避免开发者依赖 this更符合组合式API“脱离this” 的设计理念。默认情况下不能与传统的script标签同时使用即一个组件中不能有两个 script 标签特殊场景如需要配置组件选项可结合使用后续进阶部分会讲解。语法糖内部无法直接访问组件的选项式 API如data、methods、computed等因为它本身就是组合式 API 的简化写法建议全程使用组合式 API 编写逻辑。实战示例基础变量与方法template div classbase-demo h3基础用法演示/h3 p姓名{{ name }}/p p年龄{{ age }}/p p是否成年{{ isAdult }}/p button clickchangeName修改姓名/button button clickchangeAge增长年龄/button /div /template script setup // 1. 基础变量非响应式仅演示自动暴露 // 注意非响应式变量修改后模板不会自动更新 const name 张三 // 2. 响应式变量ref 包装基本类型 import { ref, computed } from vue const age ref(20) // 3. 计算属性自动暴露无需 return const isAdult computed(() age.value 18) // 4. 顶层函数自动暴露给模板 const changeName () { // 非响应式变量修改模板不更新 name 李四 // 无效模板仍显示“张三” console.log(name) // 控制台打印“李四”但模板无变化 } const changeAge () { // 响应式变量修改需通过 .value 操作 age.value // 计算属性会自动响应依赖变化 } /script关键注意点非响应式变量如 const name ‘张三’修改后模板不会更新因为 Vue 无法监听基本类型的直接赋值需使用 ref 或 reactive 包装为响应式。ref 包装的响应式变量在 script 中修改时需加.value模板中使用时无需加.valueVue 自动解包。2.2 组件导入与自动注册这是script setup最实用的特性之一组件导入后自动注册无需在 components 选项中手动声明大幅减少冗余代码。基本用法template div !-- 直接使用导入的组件无需注册 -- HelloWorld / UserCard :namename / /div /template script setup // 导入组件自动注册 import HelloWorld from ./HelloWorld.vue import UserCard from ./UserCard.vue // 定义传递给子组件的变量 const name 张三 /script进阶用法重命名组件如果导入的组件名称与当前组件内的变量/函数重名或想简化组件名称可使用 ES6 解构重命名template div !-- 使用重命名后的组件名称 -- Hello / Card :namename / /div /template script setup // 重命名导入避免命名冲突 import { default as Hello } from ./HelloWorld.vue import { default as Card } from ./UserCard.vue const name 张三 /script进阶用法动态导入组件结合动态导入懒加载优化组件加载性能适用于组件体积较大或按需加载的场景template div !-- 动态组件按需渲染 -- component :isAsyncComponent / button clickshowComponent true显示组件/button /div /template script setup import { ref, defineAsyncComponent } from vue // 动态导入组件懒加载自动注册 const AsyncComponent defineAsyncComponent(() import(./AsyncComponent.vue)) // 控制组件是否显示 const showComponent ref(false) /script注意动态导入组件时需使用defineAsyncComponent包裹这是 Vue3 提供的专门用于动态导入组件的API与setup语法糖兼容。2.3 响应式数据ref、reactive与解构setup语法糖中响应式数据的使用与setup()函数完全一致核心还是ref包装基本类型和reactive包装引用类型但需注意解构时的响应式丢失问题。1.ref用法推荐用于基本类型script setup import { ref } from vue // 基本类型响应式number、string、boolean 等 const count ref(0) const name ref(张三) const isShow ref(false) // 修改响应式数据必须加 .value const increment () { count.value } const changeName () { name.value 李四 } const toggleShow () { isShow.value !isShow.value } /script2.reactive用法推荐用于引用类型script setup import { reactive } from vue // 引用类型响应式对象、数组 const user reactive({ name: 张三, age: 20, address: { province: 广东, city: 深圳 } }) const list reactive([1, 2, 3, 4]) // 修改响应式数据无需 .value直接修改属性 const updateUser () { user.age 21 user.address.city 广州 } const addItem () { list.push(5) } /script3. 解构响应式数据避坑重点直接解构reactive包装的对象会导致响应式丢失——修改解构后的变量不会同步更新模板。解决方案使用toRefs或toRef解构。template div p姓名{{ name }}/p p年龄{{ age }}/p button clickchangeInfo修改信息/button /div /template script setup import { reactive, toRefs, toRef } from vue const user reactive({ name: 张三, age: 20 }) // 错误写法直接解构响应式丢失 // const { name, age } user // 修改 name 不会更新模板 // 正确写法1使用 toRefs 解构适用于所有属性 const { name, age } toRefs(user) // 正确写法2使用 toRef 解构适用于单个属性 // const name toRef(user, name) // const age toRef(user, age) const changeInfo () { // 解构后需加 .value 修改 name.value 李四 age.value 21 } /script关键总结ref包装的变量无论在script中还是解构后都需加.value修改。reactive包装的对象直接修改属性无需.value但解构时必须用toRefs/toRef否则丢失响应式。三、进阶用法setup语法糖核心特性掌握基础用法后我们来看setup语法糖的进阶特性这些特性能解决更多复杂场景的需求进一步提升开发效率也是面试中常考的知识点。3.1 与传统script标签结合使用默认情况下一个组件中只能有一个script setup标签但在某些场景下如需要配置组件选项name、inheritAttrs、props校验等可以结合传统的script标签使用——两个 script 标签共存各司其职。核心规则传统script标签用于配置组件选项export default 导出setup语法糖用于编写组合式逻辑两者互不冲突。template div p{{ name }}/p /div /template !-- 传统 script 标签配置组件选项 -- script export default { // 配置组件名称用于调试、递归组件等 name: MyComponent, // 关闭属性继承避免 attrs 自动绑定到根元素 inheritAttrs: false, // 组件props校验也可在 setup 中用 defineProps 定义 props: { name: { type: String, required: true, default: 默认名称 } } } /script !-- setup 语法糖编写组合式逻辑 -- script setup // 可直接使用传统 script 中定义的 props import { useAttrs } from vue // 获取组件 attrs因 inheritAttrs: false需手动绑定 const attrs useAttrs() console.log(attrs) /script常见使用场景需要配置组件 name用于调试、递归组件、KeepAlive 缓存等。需要配置 inheritAttrs、components虽然 setup 可自动注册但特殊场景可手动配置。需要编写复杂的 props 校验虽然 setup 中可通过 defineProps 定义但传统 props 选项写法更灵活。3.2 Props 定义与校验defineProps在 setup 语法糖中无法直接使用传统的 props 选项需通过 Vue3 提供的defineProps宏函数定义 props支持 props 校验、默认值等功能与传统 props 选项完全兼容。注意defineProps 是宏函数无需导入可直接在 setup 语法糖中使用Vue 自动注入。基础用法简单 props 定义template div p父组件传递的名称{{ name }}/p p父组件传递的年龄{{ age }}/p /div /template script setup // 基础写法数组形式仅定义 props 名称无校验 // const props defineProps([name, age]) // 推荐写法对象形式支持校验、默认值 const props defineProps({ name: { type: String, // 类型校验 required: true, // 是否必传 message: name 为必填项且必须是字符串 // 校验失败提示 }, age: { type: Number, required: false, default: 18, // 默认值 validator: (value) { // 自定义校验规则年龄必须大于 0 return value 0 } } }) // 使用 props无需 .value直接使用 console.log(props.name) console.log(props.age) /script进阶用法与 TypeScript 结合类型推导在 Vue3 TS 项目中defineProps 支持通过 TypeScript 类型直接定义 props无需编写繁琐的对象形式TS 会自动进行类型校验开发体验更好。script setup langts // 方式1直接通过类型定义 props无默认值 const props defineProps{ name: string age?: number // 可选属性 gender: male | female // 联合类型 }() // 方式2结合 withDefaults 定义默认值推荐 const props withDefaults( defineProps{ name: string age?: number gender?: male | female }(), { age: 18, gender: male } ) // 使用 props console.log(props.name) console.log(props.age) /script关键注意点withDefaults 是 Vue3 提供的宏函数用于给 TS 类型定义的 props 设置默认值无需导入直接使用。3.3 事件触发defineEmits在 setup 语法糖中子组件向父组件触发事件需通过defineEmits宏函数定义事件替代传统的 emits 选项支持事件校验、类型定义等功能。注意defineEmits 也是宏函数无需导入直接使用。基础用法简单事件触发!-- 子组件 Child.vue -- template button clickhandleClick触发父组件事件/button button clickhandleSend触发事件并传参/button /template script setup // 基础写法数组形式仅定义事件名称 // const emit defineEmits([click, send]) // 推荐写法对象形式支持事件参数校验 const emit defineEmits({ // 无参数事件 click: () true, // 有参数事件校验参数格式 send: (data: string) { return typeof data string } }) // 触发事件 const handleClick () { emit(click) } // 触发事件并传递参数 const handleSend () { emit(send, 子组件传递的参数) } /script!-- 父组件 Parent.vue -- template Child clickhandleChildClick sendhandleChildSend / /template script setup import Child from ./Child.vue const handleChildClick () { console.log(子组件触发了 click 事件) } const handleChildSend (data) { console.log(子组件传递的参数, data) } /script进阶用法与 TypeScript 结合script setup langts // 通过 TS 类型定义事件推荐类型自动校验 const emit defineEmits{ (e: click): void // 无参数事件 (e: send, data: string): void // 有参数事件 }() // 触发事件参数类型错误会报错 const handleSend () { emit(send, 正确参数) // 正常触发 // emit(send, 123) // TS 报错参数类型必须是 string } /script3.4 插槽使用defineSlots在 setup 语法糖中可通过defineSlots宏函数定义组件的插槽支持插槽类型校验主要用于 Vue3 TS 项目替代传统的 slots 选项。注意defineSlots 仅用于类型定义和校验无需手动注册插槽模板中可直接使用插槽。!-- 子组件 Child.vue -- lt;templategt; lt;divgt; !-- 默认插槽 -- lt;slotgt;默认内容lt;/slotgt; !-- 具名插槽 -- slot nametitlegt;默认标题lt;/slotgt; !-- 作用域插槽 -- slot nameitem :datalist/slot /div /template script setup langts import { ref } from vue const list ref([1, 2, 3]) // 定义插槽类型TS 项目推荐非 TS 项目可省略 defineSlots{ // 默认插槽 default?: () any // 具名插槽 title?: () any // 作用域插槽指定传递给父组件的参数类型 item?: (props: { data: number[] }) any }() /script!-- 父组件 Parent.vue -- templategt; lt;Childgt; !-- 默认插槽 -- div父组件默认插槽内容/divgt; !-- 具名插槽 -- template #title h2父组件标题插槽/h2 /template !-- 作用域插槽 -- template #itemslotProps div v-foritem in slotProps.data :keyitem{{ item }}/div /template /Child /template3.5 访问组件实例useAttrs 与 useSlots在 setup 语法糖中this 指向 undefined无法通过 this 访问组件实例的 attrs、slots 等属性需通过useAttrs和useSlots两个 API 访问。1. useAttrs访问组件的 attrsattrs 包含父组件传递的、未被 props 接收的属性如 class、style、自定义属性等与传统的 this.$attrs 功能一致。template div :classattrs.class :styleattrs.style {{ attrs.customAttr }} /div /template script setup import { useAttrs } from vue // 获取 attrs 对象 const attrs useAttrs() // 访问 attrs 中的属性 console.log(attrs.customAttr) console.log(attrs.class) /script2. useSlots访问组件的 slotsslots 包含父组件传递的所有插槽内容与传统的 this.$slots 功能一致主要用于在 script 中操作插槽如判断插槽是否存在。script setup import { useSlots } from vue // 获取 slots 对象 const slots useSlots() // 判断默认插槽是否存在 console.log(slots.default) // 存在则返回插槽内容不存在则返回 undefined // 判断具名插槽是否存在 console.log(slots.title) /script四、实战场景setup 语法糖常用组合用法结合前面的知识点我们通过几个实战场景演示 setup 语法糖的常用组合用法覆盖日常开发中最常见的场景帮助你快速上手。4.1 场景1基础页面开发变量、方法、组件引入template div classhome-page Header title首页 / div classcontent h3欢迎来到首页{{ username }}/h3 p当前登录时长{{ loginTime }} 秒/p button clicklogout退出登录/button /div Footer / /div /template script setup // 引入组件自动注册 import Header from ./components/Header.vue import Footer from ./components/Footer.vue // 响应式变量 import { ref, onMounted } from vue const username ref(张三) const loginTime ref(0) // 方法 const logout () { alert(退出登录成功) // 实际开发中可结合路由跳转 } // 生命周期钩子直接使用无需注册 onMounted(() { // 模拟登录时长统计 setInterval(() { loginTime.value }, 1000) }) /script style scoped .home-page { min-height: 100vh; display: flex; flex-direction: column; } .content { flex: 1; padding: 20px; } /style4.2 场景2子父组件通信props emit!-- 子组件 TodoItem.vue -- template div classtodo-item :class{ completed: props.completed } input typecheckbox v-modelprops.completed changehandleChange span{{ props.content }}/span button clickhandleDelete删除/button /div /template script setup // 定义 props const props defineProps({ content: { type: String, required: true }, completed: { type: Boolean, default: false }, index: { type: Number, required: true } }) // 定义 emit const emit defineEmits([change, delete]) // 触发事件传递参数 const handleChange () { emit(change, props.index, props.completed) } const handleDelete () { emit(delete, props.index) } /script!-- 父组件 TodoList.vue -- template div classtodo-list h3待办列表/h3 input typetext v-modelinputVal keyup.enteraddTodo placeholder请输入待办内容 TodoItem v-for(item, index) in todoList :keyindex :contentitem.content :completeditem.completed :indexindex changehandleTodoChange deletehandleTodoDelete / /div /template script setup import { ref } from vue import TodoItem from ./TodoItem.vue // 响应式数据 const inputVal ref() const todoList ref([ { content: 学习 setup 语法糖, completed: false }, { content: 完成 Vue3 实战, completed: true } ]) // 添加待办 const addTodo () { if (!inputVal.value.trim()) return todoList.value.push({ content: inputVal.value, completed: false }) inputVal.value } // 修改待办状态 const handleTodoChange (index, completed) { todoList.value[index].completed completed } // 删除待办 const handleTodoDelete (index) { todoList.value.splice(index, 1) } /script4.3 场景3Vue3 TS 组合使用完整示例template div classuser-info h3用户信息/h3 p姓名{{ user.name }}/p p年龄{{ user.age }}/p p性别{{ user.gender }}/p button clickupdateAge年龄1/button /div /template script setup langts // 定义用户类型接口 interface User { name: string age: number gender: male | female } // 响应式数据指定类型 import { ref, computed } from vue const user refUser({ name: 张三, age: 20, gender: male }) // 计算属性类型自动推导 const isAdult computed(() user.value.age 18) // 方法指定参数和返回值类型 const updateAge (): void { user.value.age } // 打印用户信息类型校验 console.log(user.value.name) // console.log(user.value.address) // TS 报错User 接口中无 address 属性 /script五、避坑指南setup 语法糖常见错误与解决方案在使用 setup 语法糖的过程中新手容易遇到一些问题这里整理了最常见的 5 个坑附上解决方案帮助你避免踩坑。5.1 坑1解构 reactive 数据导致响应式丢失错误表现解构 reactive 包装的对象后修改解构后的变量模板不更新。script setup import { reactive } from vue const user reactive({ name: 张三, age: 20 }) // 错误直接解构响应式丢失 const { name, age } user const changeName () { name 李四 // 模板不更新 } /script解决方案使用 toRefs 或 toRef 解构修改时加 .value。script setup import { reactive, toRefs } from vue const user reactive({ name: 张三, age: 20 }) // 正确使用 toRefs 解构 const { name, age } toRefs(user) const changeName () { name.value 李四 // 模板正常更新 } /script5.2 坑2忘记给 ref 变量加 .value 修改错误表现修改 ref 包装的变量时忘记加 .value导致变量修改失败模板不更新。script setup import { ref } from vue const count ref(0) const increment () { // 错误忘记加 .value count // 无效count 仍为 0 } /script解决方案修改 ref 变量时必须加 .value模板中使用时无需加。script setup import { ref } from vue const count ref(0) const increment () { // 正确加 .value count.value } /script5.3 坑3同时使用两个 script 标签未区分功能错误表现在一个组件中使用两个 script 标签且都写了组合式逻辑导致逻辑冲突、变量未定义。!-- 错误写法 -- script // 传统 script 标签写组合式逻辑 import { ref } from vue const count ref(0) /script script setup // 语法糖中也写逻辑导致 count 未定义 console.log(count) // 报错count is not defined /script解决方案两个 script 标签分工明确——传统 script 标签仅用于配置组件选项name、props 等setup 语法糖用于编写组合式逻辑且组合式逻辑仅在 setup 语法糖中编写。5.4 坑4使用 this 访问组件实例错误表现在 setup 语法糖中使用 this试图访问 props、emit、attrs 等导致报错this is undefined。script setup // 错误使用 this console.log(this.props.name) // 报错Cannot read property props of undefined /script解决方案放弃使用 this通过 defineProps、defineEmits、useAttrs 等 API 访问对应内容。5.5 坑5组件导入后未使用导致报错错误表现导入组件后未在模板中使用Vue3 会报“组件未使用”的警告部分严格模式下会报错。script setup // 错误导入组件但未使用 import HelloWorld from ./HelloWorld.vue /script解决方案要么在模板中使用导入的组件要么删除未使用的导入语句如果确实需要导入但不使用如动态导入备用组件可在导入语句后加// ts-ignoreTS 项目或忽略警告。六、总结与拓展6.1 核心总结script setup语法糖是 Vue3 组合式 API 的简化写法核心优势是简化代码、提升效率无需手动 return 暴露、无需手动注册组件与 TypeScript 结合良好是当前 Vue3 开发的主流选择。核心知识点梳理基础用法添加 setup 关键字顶层变量/方法自动暴露无需 return。组件导入自动注册支持重命名、动态导入。响应式数据ref基本类型、reactive引用类型解构用 toRefs/toRef。进阶特性definePropsprops 定义、defineEmits事件触发、defineSlots插槽定义。避坑重点避免解构 reactive 丢失响应式、ref 变量修改加 .value、不使用 this。6.2 拓展延伸与其他 API 结合setup 语法糖可与 Vue3 其他 API 无缝结合如useRouter路由、useStorePinia/Vuex、useFetch请求等后续会单独讲解。生命周期钩子setup 语法糖中可直接使用 Vue3 的生命周期钩子如 onMounted、onUpdated 等无需注册直接导入使用即可。兼容性setup 语法糖从 Vue3.2 版本开始支持如果你使用的是 Vue3.0 或 3.1 版本需升级 Vue 版本才能使用。掌握 setup 语法糖能大幅提升你的 Vue3 开发效率减少冗余代码让组合式逻辑更清晰。建议多动手实战结合本文的示例尝试在项目中使用快速吃透所有用法。
setup 语法糖从 0 到 1 实战教程
发布时间:2026/5/18 21:59:10
本文是 Vue3 组合式 API 系列的进阶篇聚焦script setup语法糖的全部用法、实战场景、避坑技巧从基础入门到高级实战手把手教你吃透setup语法糖大幅提升 Vue3 开发效率。前置要求掌握 Vue3 组合式 API 基础ref、reactive、computed等了解 Vue2 选项式 API 与 Vue3 组合式 API 的区别具备基础 Vue3 项目开发能力。一、前言为什么要使用在 Vue3 推出组合式 API 初期我们使用setup()函数作为组合式逻辑的入口虽然解决了选项式 API 逻辑碎片化的问题比如一个业务逻辑的代码分散在data、methods、computed中但在实际开发中存在三个明显痛点严重影响开发效率手动暴露冗余所有需要在模板中使用的变量、方法都必须手动通过return暴露代码冗余且容易遗漏一旦遗漏就会导致模板渲染失败排查起来耗时费力组件注册繁琐组件导入后必须在components选项中手动注册如components: { XXX }哪怕是常用的基础组件也需要重复注册增加不必要的代码量TS 适配不佳与 TypeScript 结合时类型声明繁琐需要手动定义接口、标注类型开发体验不够流畅难以发挥 TS 的类型校验优势。为了解决这些问题Vue3.2 版本正式推出script setup语法糖它并非新增功能而是setup()函数的“语法糖简化版”——保留了组合式 API 的所有功能同时大幅简化代码、提升开发效率目前已成为 Vue3 开发的主流写法几乎所有 Vue3 项目包括 Vue3 TS 项目都会优先使用。先看一组直观对比快速感受语法糖的优势1.1 传统 setup() 函数写法template div p{{ count }}/p button clickincrement1/button HelloWorld / /div /template script import { ref } from vue import HelloWorld from ./HelloWorld.vue export default { // 手动注册组件哪怕只导入一个也需要写 components: { HelloWorld }, // 手动定义 setup 函数作为组合式逻辑入口 setup() { // 定义响应式变量 const count ref(0) // 定义方法 const increment () count.value // 手动 return 暴露模板才能使用少写一个就报错 return { count, increment } } } /script1.2script setup语法糖写法template div p{{ count }}/p button clickincrement1/button HelloWorld / /div /template !-- 只需在 script 标签上添加 setup 关键字无需其他多余配置 -- script setup import { ref } from vue // 导入组件自动注册无需手动写 components 选项 import HelloWorld from ./HelloWorld.vue // 顶层声明的变量、方法自动暴露给模板无需 return const count ref(0) const increment () count.value /script对比可见语法糖写法删除了冗余的export default、components注册、return暴露代码量减少近一半且逻辑更清晰——所有组合式逻辑都能在顶层直接编写无需嵌套在 setup 函数中。接下来我们从 0 到 1 实战掌握它的所有用法覆盖基础、进阶、实战、避坑全场景。二、基础入门script setup核心基础用法这一部分是语法糖的基础也是日常开发中最常用的内容必须熟练掌握。核心原则script setup内部的代码本质上就是setup()函数的函数体所有规则与setup()函数一致只是简化了写法。2.1 基本使用语法格式与自动暴露script setup的使用非常简单只需在script标签上添加setup关键字即可无需额外配置核心规则如下重点记顶层声明的变量、函数、类会自动暴露给模板使用无需手动return注意仅顶层声明有效嵌套在函数内部的变量/方法不会自动暴露。语法糖内部的代码相当于在setup()函数内部执行this指向undefined刻意设计避免开发者依赖 this更符合组合式API“脱离this” 的设计理念。默认情况下不能与传统的script标签同时使用即一个组件中不能有两个 script 标签特殊场景如需要配置组件选项可结合使用后续进阶部分会讲解。语法糖内部无法直接访问组件的选项式 API如data、methods、computed等因为它本身就是组合式 API 的简化写法建议全程使用组合式 API 编写逻辑。实战示例基础变量与方法template div classbase-demo h3基础用法演示/h3 p姓名{{ name }}/p p年龄{{ age }}/p p是否成年{{ isAdult }}/p button clickchangeName修改姓名/button button clickchangeAge增长年龄/button /div /template script setup // 1. 基础变量非响应式仅演示自动暴露 // 注意非响应式变量修改后模板不会自动更新 const name 张三 // 2. 响应式变量ref 包装基本类型 import { ref, computed } from vue const age ref(20) // 3. 计算属性自动暴露无需 return const isAdult computed(() age.value 18) // 4. 顶层函数自动暴露给模板 const changeName () { // 非响应式变量修改模板不更新 name 李四 // 无效模板仍显示“张三” console.log(name) // 控制台打印“李四”但模板无变化 } const changeAge () { // 响应式变量修改需通过 .value 操作 age.value // 计算属性会自动响应依赖变化 } /script关键注意点非响应式变量如 const name ‘张三’修改后模板不会更新因为 Vue 无法监听基本类型的直接赋值需使用 ref 或 reactive 包装为响应式。ref 包装的响应式变量在 script 中修改时需加.value模板中使用时无需加.valueVue 自动解包。2.2 组件导入与自动注册这是script setup最实用的特性之一组件导入后自动注册无需在 components 选项中手动声明大幅减少冗余代码。基本用法template div !-- 直接使用导入的组件无需注册 -- HelloWorld / UserCard :namename / /div /template script setup // 导入组件自动注册 import HelloWorld from ./HelloWorld.vue import UserCard from ./UserCard.vue // 定义传递给子组件的变量 const name 张三 /script进阶用法重命名组件如果导入的组件名称与当前组件内的变量/函数重名或想简化组件名称可使用 ES6 解构重命名template div !-- 使用重命名后的组件名称 -- Hello / Card :namename / /div /template script setup // 重命名导入避免命名冲突 import { default as Hello } from ./HelloWorld.vue import { default as Card } from ./UserCard.vue const name 张三 /script进阶用法动态导入组件结合动态导入懒加载优化组件加载性能适用于组件体积较大或按需加载的场景template div !-- 动态组件按需渲染 -- component :isAsyncComponent / button clickshowComponent true显示组件/button /div /template script setup import { ref, defineAsyncComponent } from vue // 动态导入组件懒加载自动注册 const AsyncComponent defineAsyncComponent(() import(./AsyncComponent.vue)) // 控制组件是否显示 const showComponent ref(false) /script注意动态导入组件时需使用defineAsyncComponent包裹这是 Vue3 提供的专门用于动态导入组件的API与setup语法糖兼容。2.3 响应式数据ref、reactive与解构setup语法糖中响应式数据的使用与setup()函数完全一致核心还是ref包装基本类型和reactive包装引用类型但需注意解构时的响应式丢失问题。1.ref用法推荐用于基本类型script setup import { ref } from vue // 基本类型响应式number、string、boolean 等 const count ref(0) const name ref(张三) const isShow ref(false) // 修改响应式数据必须加 .value const increment () { count.value } const changeName () { name.value 李四 } const toggleShow () { isShow.value !isShow.value } /script2.reactive用法推荐用于引用类型script setup import { reactive } from vue // 引用类型响应式对象、数组 const user reactive({ name: 张三, age: 20, address: { province: 广东, city: 深圳 } }) const list reactive([1, 2, 3, 4]) // 修改响应式数据无需 .value直接修改属性 const updateUser () { user.age 21 user.address.city 广州 } const addItem () { list.push(5) } /script3. 解构响应式数据避坑重点直接解构reactive包装的对象会导致响应式丢失——修改解构后的变量不会同步更新模板。解决方案使用toRefs或toRef解构。template div p姓名{{ name }}/p p年龄{{ age }}/p button clickchangeInfo修改信息/button /div /template script setup import { reactive, toRefs, toRef } from vue const user reactive({ name: 张三, age: 20 }) // 错误写法直接解构响应式丢失 // const { name, age } user // 修改 name 不会更新模板 // 正确写法1使用 toRefs 解构适用于所有属性 const { name, age } toRefs(user) // 正确写法2使用 toRef 解构适用于单个属性 // const name toRef(user, name) // const age toRef(user, age) const changeInfo () { // 解构后需加 .value 修改 name.value 李四 age.value 21 } /script关键总结ref包装的变量无论在script中还是解构后都需加.value修改。reactive包装的对象直接修改属性无需.value但解构时必须用toRefs/toRef否则丢失响应式。三、进阶用法setup语法糖核心特性掌握基础用法后我们来看setup语法糖的进阶特性这些特性能解决更多复杂场景的需求进一步提升开发效率也是面试中常考的知识点。3.1 与传统script标签结合使用默认情况下一个组件中只能有一个script setup标签但在某些场景下如需要配置组件选项name、inheritAttrs、props校验等可以结合传统的script标签使用——两个 script 标签共存各司其职。核心规则传统script标签用于配置组件选项export default 导出setup语法糖用于编写组合式逻辑两者互不冲突。template div p{{ name }}/p /div /template !-- 传统 script 标签配置组件选项 -- script export default { // 配置组件名称用于调试、递归组件等 name: MyComponent, // 关闭属性继承避免 attrs 自动绑定到根元素 inheritAttrs: false, // 组件props校验也可在 setup 中用 defineProps 定义 props: { name: { type: String, required: true, default: 默认名称 } } } /script !-- setup 语法糖编写组合式逻辑 -- script setup // 可直接使用传统 script 中定义的 props import { useAttrs } from vue // 获取组件 attrs因 inheritAttrs: false需手动绑定 const attrs useAttrs() console.log(attrs) /script常见使用场景需要配置组件 name用于调试、递归组件、KeepAlive 缓存等。需要配置 inheritAttrs、components虽然 setup 可自动注册但特殊场景可手动配置。需要编写复杂的 props 校验虽然 setup 中可通过 defineProps 定义但传统 props 选项写法更灵活。3.2 Props 定义与校验defineProps在 setup 语法糖中无法直接使用传统的 props 选项需通过 Vue3 提供的defineProps宏函数定义 props支持 props 校验、默认值等功能与传统 props 选项完全兼容。注意defineProps 是宏函数无需导入可直接在 setup 语法糖中使用Vue 自动注入。基础用法简单 props 定义template div p父组件传递的名称{{ name }}/p p父组件传递的年龄{{ age }}/p /div /template script setup // 基础写法数组形式仅定义 props 名称无校验 // const props defineProps([name, age]) // 推荐写法对象形式支持校验、默认值 const props defineProps({ name: { type: String, // 类型校验 required: true, // 是否必传 message: name 为必填项且必须是字符串 // 校验失败提示 }, age: { type: Number, required: false, default: 18, // 默认值 validator: (value) { // 自定义校验规则年龄必须大于 0 return value 0 } } }) // 使用 props无需 .value直接使用 console.log(props.name) console.log(props.age) /script进阶用法与 TypeScript 结合类型推导在 Vue3 TS 项目中defineProps 支持通过 TypeScript 类型直接定义 props无需编写繁琐的对象形式TS 会自动进行类型校验开发体验更好。script setup langts // 方式1直接通过类型定义 props无默认值 const props defineProps{ name: string age?: number // 可选属性 gender: male | female // 联合类型 }() // 方式2结合 withDefaults 定义默认值推荐 const props withDefaults( defineProps{ name: string age?: number gender?: male | female }(), { age: 18, gender: male } ) // 使用 props console.log(props.name) console.log(props.age) /script关键注意点withDefaults 是 Vue3 提供的宏函数用于给 TS 类型定义的 props 设置默认值无需导入直接使用。3.3 事件触发defineEmits在 setup 语法糖中子组件向父组件触发事件需通过defineEmits宏函数定义事件替代传统的 emits 选项支持事件校验、类型定义等功能。注意defineEmits 也是宏函数无需导入直接使用。基础用法简单事件触发!-- 子组件 Child.vue -- template button clickhandleClick触发父组件事件/button button clickhandleSend触发事件并传参/button /template script setup // 基础写法数组形式仅定义事件名称 // const emit defineEmits([click, send]) // 推荐写法对象形式支持事件参数校验 const emit defineEmits({ // 无参数事件 click: () true, // 有参数事件校验参数格式 send: (data: string) { return typeof data string } }) // 触发事件 const handleClick () { emit(click) } // 触发事件并传递参数 const handleSend () { emit(send, 子组件传递的参数) } /script!-- 父组件 Parent.vue -- template Child clickhandleChildClick sendhandleChildSend / /template script setup import Child from ./Child.vue const handleChildClick () { console.log(子组件触发了 click 事件) } const handleChildSend (data) { console.log(子组件传递的参数, data) } /script进阶用法与 TypeScript 结合script setup langts // 通过 TS 类型定义事件推荐类型自动校验 const emit defineEmits{ (e: click): void // 无参数事件 (e: send, data: string): void // 有参数事件 }() // 触发事件参数类型错误会报错 const handleSend () { emit(send, 正确参数) // 正常触发 // emit(send, 123) // TS 报错参数类型必须是 string } /script3.4 插槽使用defineSlots在 setup 语法糖中可通过defineSlots宏函数定义组件的插槽支持插槽类型校验主要用于 Vue3 TS 项目替代传统的 slots 选项。注意defineSlots 仅用于类型定义和校验无需手动注册插槽模板中可直接使用插槽。!-- 子组件 Child.vue -- lt;templategt; lt;divgt; !-- 默认插槽 -- lt;slotgt;默认内容lt;/slotgt; !-- 具名插槽 -- slot nametitlegt;默认标题lt;/slotgt; !-- 作用域插槽 -- slot nameitem :datalist/slot /div /template script setup langts import { ref } from vue const list ref([1, 2, 3]) // 定义插槽类型TS 项目推荐非 TS 项目可省略 defineSlots{ // 默认插槽 default?: () any // 具名插槽 title?: () any // 作用域插槽指定传递给父组件的参数类型 item?: (props: { data: number[] }) any }() /script!-- 父组件 Parent.vue -- templategt; lt;Childgt; !-- 默认插槽 -- div父组件默认插槽内容/divgt; !-- 具名插槽 -- template #title h2父组件标题插槽/h2 /template !-- 作用域插槽 -- template #itemslotProps div v-foritem in slotProps.data :keyitem{{ item }}/div /template /Child /template3.5 访问组件实例useAttrs 与 useSlots在 setup 语法糖中this 指向 undefined无法通过 this 访问组件实例的 attrs、slots 等属性需通过useAttrs和useSlots两个 API 访问。1. useAttrs访问组件的 attrsattrs 包含父组件传递的、未被 props 接收的属性如 class、style、自定义属性等与传统的 this.$attrs 功能一致。template div :classattrs.class :styleattrs.style {{ attrs.customAttr }} /div /template script setup import { useAttrs } from vue // 获取 attrs 对象 const attrs useAttrs() // 访问 attrs 中的属性 console.log(attrs.customAttr) console.log(attrs.class) /script2. useSlots访问组件的 slotsslots 包含父组件传递的所有插槽内容与传统的 this.$slots 功能一致主要用于在 script 中操作插槽如判断插槽是否存在。script setup import { useSlots } from vue // 获取 slots 对象 const slots useSlots() // 判断默认插槽是否存在 console.log(slots.default) // 存在则返回插槽内容不存在则返回 undefined // 判断具名插槽是否存在 console.log(slots.title) /script四、实战场景setup 语法糖常用组合用法结合前面的知识点我们通过几个实战场景演示 setup 语法糖的常用组合用法覆盖日常开发中最常见的场景帮助你快速上手。4.1 场景1基础页面开发变量、方法、组件引入template div classhome-page Header title首页 / div classcontent h3欢迎来到首页{{ username }}/h3 p当前登录时长{{ loginTime }} 秒/p button clicklogout退出登录/button /div Footer / /div /template script setup // 引入组件自动注册 import Header from ./components/Header.vue import Footer from ./components/Footer.vue // 响应式变量 import { ref, onMounted } from vue const username ref(张三) const loginTime ref(0) // 方法 const logout () { alert(退出登录成功) // 实际开发中可结合路由跳转 } // 生命周期钩子直接使用无需注册 onMounted(() { // 模拟登录时长统计 setInterval(() { loginTime.value }, 1000) }) /script style scoped .home-page { min-height: 100vh; display: flex; flex-direction: column; } .content { flex: 1; padding: 20px; } /style4.2 场景2子父组件通信props emit!-- 子组件 TodoItem.vue -- template div classtodo-item :class{ completed: props.completed } input typecheckbox v-modelprops.completed changehandleChange span{{ props.content }}/span button clickhandleDelete删除/button /div /template script setup // 定义 props const props defineProps({ content: { type: String, required: true }, completed: { type: Boolean, default: false }, index: { type: Number, required: true } }) // 定义 emit const emit defineEmits([change, delete]) // 触发事件传递参数 const handleChange () { emit(change, props.index, props.completed) } const handleDelete () { emit(delete, props.index) } /script!-- 父组件 TodoList.vue -- template div classtodo-list h3待办列表/h3 input typetext v-modelinputVal keyup.enteraddTodo placeholder请输入待办内容 TodoItem v-for(item, index) in todoList :keyindex :contentitem.content :completeditem.completed :indexindex changehandleTodoChange deletehandleTodoDelete / /div /template script setup import { ref } from vue import TodoItem from ./TodoItem.vue // 响应式数据 const inputVal ref() const todoList ref([ { content: 学习 setup 语法糖, completed: false }, { content: 完成 Vue3 实战, completed: true } ]) // 添加待办 const addTodo () { if (!inputVal.value.trim()) return todoList.value.push({ content: inputVal.value, completed: false }) inputVal.value } // 修改待办状态 const handleTodoChange (index, completed) { todoList.value[index].completed completed } // 删除待办 const handleTodoDelete (index) { todoList.value.splice(index, 1) } /script4.3 场景3Vue3 TS 组合使用完整示例template div classuser-info h3用户信息/h3 p姓名{{ user.name }}/p p年龄{{ user.age }}/p p性别{{ user.gender }}/p button clickupdateAge年龄1/button /div /template script setup langts // 定义用户类型接口 interface User { name: string age: number gender: male | female } // 响应式数据指定类型 import { ref, computed } from vue const user refUser({ name: 张三, age: 20, gender: male }) // 计算属性类型自动推导 const isAdult computed(() user.value.age 18) // 方法指定参数和返回值类型 const updateAge (): void { user.value.age } // 打印用户信息类型校验 console.log(user.value.name) // console.log(user.value.address) // TS 报错User 接口中无 address 属性 /script五、避坑指南setup 语法糖常见错误与解决方案在使用 setup 语法糖的过程中新手容易遇到一些问题这里整理了最常见的 5 个坑附上解决方案帮助你避免踩坑。5.1 坑1解构 reactive 数据导致响应式丢失错误表现解构 reactive 包装的对象后修改解构后的变量模板不更新。script setup import { reactive } from vue const user reactive({ name: 张三, age: 20 }) // 错误直接解构响应式丢失 const { name, age } user const changeName () { name 李四 // 模板不更新 } /script解决方案使用 toRefs 或 toRef 解构修改时加 .value。script setup import { reactive, toRefs } from vue const user reactive({ name: 张三, age: 20 }) // 正确使用 toRefs 解构 const { name, age } toRefs(user) const changeName () { name.value 李四 // 模板正常更新 } /script5.2 坑2忘记给 ref 变量加 .value 修改错误表现修改 ref 包装的变量时忘记加 .value导致变量修改失败模板不更新。script setup import { ref } from vue const count ref(0) const increment () { // 错误忘记加 .value count // 无效count 仍为 0 } /script解决方案修改 ref 变量时必须加 .value模板中使用时无需加。script setup import { ref } from vue const count ref(0) const increment () { // 正确加 .value count.value } /script5.3 坑3同时使用两个 script 标签未区分功能错误表现在一个组件中使用两个 script 标签且都写了组合式逻辑导致逻辑冲突、变量未定义。!-- 错误写法 -- script // 传统 script 标签写组合式逻辑 import { ref } from vue const count ref(0) /script script setup // 语法糖中也写逻辑导致 count 未定义 console.log(count) // 报错count is not defined /script解决方案两个 script 标签分工明确——传统 script 标签仅用于配置组件选项name、props 等setup 语法糖用于编写组合式逻辑且组合式逻辑仅在 setup 语法糖中编写。5.4 坑4使用 this 访问组件实例错误表现在 setup 语法糖中使用 this试图访问 props、emit、attrs 等导致报错this is undefined。script setup // 错误使用 this console.log(this.props.name) // 报错Cannot read property props of undefined /script解决方案放弃使用 this通过 defineProps、defineEmits、useAttrs 等 API 访问对应内容。5.5 坑5组件导入后未使用导致报错错误表现导入组件后未在模板中使用Vue3 会报“组件未使用”的警告部分严格模式下会报错。script setup // 错误导入组件但未使用 import HelloWorld from ./HelloWorld.vue /script解决方案要么在模板中使用导入的组件要么删除未使用的导入语句如果确实需要导入但不使用如动态导入备用组件可在导入语句后加// ts-ignoreTS 项目或忽略警告。六、总结与拓展6.1 核心总结script setup语法糖是 Vue3 组合式 API 的简化写法核心优势是简化代码、提升效率无需手动 return 暴露、无需手动注册组件与 TypeScript 结合良好是当前 Vue3 开发的主流选择。核心知识点梳理基础用法添加 setup 关键字顶层变量/方法自动暴露无需 return。组件导入自动注册支持重命名、动态导入。响应式数据ref基本类型、reactive引用类型解构用 toRefs/toRef。进阶特性definePropsprops 定义、defineEmits事件触发、defineSlots插槽定义。避坑重点避免解构 reactive 丢失响应式、ref 变量修改加 .value、不使用 this。6.2 拓展延伸与其他 API 结合setup 语法糖可与 Vue3 其他 API 无缝结合如useRouter路由、useStorePinia/Vuex、useFetch请求等后续会单独讲解。生命周期钩子setup 语法糖中可直接使用 Vue3 的生命周期钩子如 onMounted、onUpdated 等无需注册直接导入使用即可。兼容性setup 语法糖从 Vue3.2 版本开始支持如果你使用的是 Vue3.0 或 3.1 版本需升级 Vue 版本才能使用。掌握 setup 语法糖能大幅提升你的 Vue3 开发效率减少冗余代码让组合式逻辑更清晰。建议多动手实战结合本文的示例尝试在项目中使用快速吃透所有用法。