Vue3 script setup 语法糖最全教程!零基础吃透+项目落地+面试满分

一、前言:什么是 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业务开发场景
相关推荐
ConardLi1 小时前
Harness 实践:让 Agent 全自动制作知识讲解视频
前端·人工智能·后端
努力干饭中1 小时前
Vibe Coding 第二弹:做一个 Canvas K线图
前端·canvas·vibecoding
卷帘依旧2 小时前
Vue 响应式原理:Object.defineProperty vs Proxy 深度对比
前端·vue.js
yqcoder2 小时前
原生 AJAX 揭秘:如何使用 XHR 发起请求
前端·ajax·okhttp
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_34:(深入XML 中的 CDATASection 接口)
xml·前端·html·html5·媒体
之歆2 小时前
DAY_20JavaScript 条件语句与循环结构深度学习(二)
前端·javascript
山北雨夜漫步2 小时前
LangGraph
java·前端·算法
漓漾li2 小时前
每日面试题-前端
前端·react.js·面试
布局呆星2 小时前
Vue3 路由守卫详解:全局守卫、路由独享守卫、组件内守卫
前端·javascript·vue.js