Vue 3 中编写单文件组件(SFC)的编译时语法糖:<script setup>

Vue3的<script setup>语法糖简化了Composition API的使用,主要特点包括:

  • 自动暴露顶级变量给模板
  • 组件自动注册
  • 使用defineProps/defineEmits声明属性和事件
  • 通过defineExpose暴露方法

它显著减少了样板代码,支持TypeScript类型推断和顶层await,使组件开发更简洁高效。


相比传统Options API,<script setup>代码量减少约30%,更适合现代JavaScript开发模式,是Vue3项目的推荐写法,尤其适合需要良好类型支持和追求开发效率的场景。


<script setup>


<script setup>Vue 3 中编写单文件组件(SFC)的编译时语法糖,它让 Composition API 的代码更加简洁、直观。


这是 Vue 3 最重要的开发体验改进之一。


基本语法

vue

javascript 复制代码
<!-- 传统 Options API -->
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<!-- 使用 <script setup> -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

可以看到,代码量显著减少,更符合 JavaScript 的自然写法。


核心特性

1. 顶级变量自动暴露给模板

<script setup> 中声明的所有顶级变量 (包括导入的)都会自动暴露给模板,无需 return

vue

javascript 复制代码
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'

// 自动暴露给模板
const count = ref(0)
const message = 'Hello Vue 3!'
const doubleCount = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <!-- 所有顶级变量都可以直接使用 -->
  <MyComponent />
  <h1>{{ message }}</h1>
  <p>{{ count }} x 2 = {{ doubleCount }}</p>
  <button @click="increment">+1</button>
</template>

2. 组件自动注册

导入的组件无需在 components 选项中注册,可以直接在模板中使用。

vue

javascript 复制代码
<script setup>
// 导入后直接可用
import Button from './Button.vue'
import Modal from './Modal.vue'
import Icon from './Icon.vue'
</script>

<template>
  <Button>点击</Button>
  <Modal />
  <Icon name="user" />
</template>

3. 定义 Props 和 Emits

使用 definePropsdefineEmits 编译器宏来声明:

vue

javascript 复制代码
<script setup>
// 定义 Props
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
})

// 定义 Emits(两种方式)
// 方式1:数组形式
const emit = defineEmits(['update:title', 'submit'])

// 方式2:对象形式(可添加验证)
const emit = defineEmits({
  'update:title': (value) => {
    return typeof value === 'string'
  },
  'submit': null  // 无需验证
})

// 触发事件
const updateTitle = () => {
  emit('update:title', '新标题')
}
</script>

<template>
  <h1>{{ title }}</h1>
  <p>计数: {{ count }}</p>
</template>

4. 使用 defineExpose 暴露组件方法

默认情况下,<script setup> 中的变量是私有的 。如果需要父组件访问,需要使用 defineExpose

javascript 复制代码
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const reset = () => {
  count.value = 0
}

// 只暴露 reset 方法,count 保持私有
defineExpose({
  reset
})
</script>

<!-- Parent.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const childRef = ref(null)

onMounted(() => {
  // 只能访问暴露的方法
  childRef.value?.reset()  // ✅ 可以调用
  console.log(childRef.value?.count)  // ❌ undefined(私有)
})
</script>

<template>
  <Child ref="childRef" />
</template>

5. 使用 useSlotsuseAttrs

javascript 复制代码
<script setup>
import { useSlots, useAttrs } from 'vue'

// 访问插槽
const slots = useSlots()
// 访问非 props 的属性(class, style, 自定义属性等)
const attrs = useAttrs()

console.log(slots.default)  // 默认插槽内容
console.log(attrs.class)    // 传递的 class
</script>

高级用法

1. 与 TypeScript 完美集成

javascript 复制代码
<script setup lang="ts">
// 使用类型声明 Props 和 Emits
interface Props {
  title: string
  count?: number
}

interface Emits {
  (e: 'update:title', value: string): void
  (e: 'submit'): void
}

// 带类型的 Props
const props = withDefaults(defineProps<Props>(), {
  count: 0
})

// 带类型的 Emits
const emit = defineEmits<Emits>()

// 自动类型推断
const doubleCount = computed(() => (props.count || 0) * 2)
</script>

2. 顶层 await

<script setup> 支持顶层 await,结果会自动编译成 async setup()

vue

javascript 复制代码
<script setup>
// 直接使用 await,无需包装
const user = await fetchUser()
const posts = await fetchPosts()

// 甚至可以在模板中使用
const data = await fetchData()
</script>

<template>
  <div v-if="data">
    {{ data }}
  </div>
</template>

3. 动态组件

vue

javascript 复制代码
<script setup>
import { shallowRef } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
import Contact from './Contact.vue'

const components = {
  Home,
  About,
  Contact
}

const currentTab = shallowRef('Home')
const currentComponent = computed(() => components[currentTab.value])
</script>

<template>
  <button 
    v-for="tab in Object.keys(components)" 
    :key="tab"
    @click="currentTab = tab"
  >
    {{ tab }}
  </button>
  
  <component :is="currentComponent" />
</template>

4. 组合式函数的使用

javascript 复制代码
<script setup>
import { useMouse, useLocalStorage } from './composables'

// 直接使用组合式函数
const { x, y } = useMouse()
const { value: theme } = useLocalStorage('theme', 'light')

// 组合多个功能
const {
  data,
  loading,
  error,
  fetchData
} = useFetch('/api/data')
</script>

与传统 <script> 的对比

特性 <script setup> 传统 <script>
代码量 更简洁,减少 ~30% 需要更多的样板代码
组件注册 自动注册,无需 components 选项 需要在 components 中注册
暴露变量 自动暴露顶级变量 需要从 setup() 中返回
Props/Emits 使用 defineProps/defineEmits 编译器宏 使用 propsemits 选项
TypeScript 更好的类型推断和集成 类型支持有限
顶层 await 支持 不支持
学习曲线 对 React Hooks 用户更友好 对 Vue 2 用户更熟悉

常见问题和解决方案

问题1:需要访问组件实例?

vue

javascript 复制代码
<script setup>
import { getCurrentInstance } from 'vue'

// 获取当前组件实例(谨慎使用)
const instance = getCurrentInstance()

// 访问全局属性
console.log(instance.appContext.config.globalProperties.$router)

// 尽量使用 Composition API 替代
// 例如使用 useRouter() 而不是 this.$router
</script>

问题2:与 Options API 混用?

vue

javascript 复制代码
<!-- 可以同时使用,但不推荐 -->
<script>
export default {
  inheritAttrs: false
}
</script>

<script setup>
// Composition API 代码
</script>

问题3:Props 的响应式解构

vue

javascript 复制代码
<script setup>
import { toRefs } from 'vue'

const props = defineProps({
  user: Object,
  active: Boolean
})

// ❌ 错误:直接解构会丢失响应式
const { user, active } = props

// ✅ 正确:使用 toRefs 保持响应式
const { user, active } = toRefs(props)

// 或者使用 computed
const userName = computed(() => props.user?.name)
</script>

最佳实践

1. 统一使用 <script setup>

在新项目中,默认使用 <script setup>,保持代码风格统一。

2. 合理组织代码顺序

javascript 复制代码
<script setup>
// 1. 导入依赖
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'

// 2. 组件导入
import Button from './Button.vue'
import Modal from './Modal.vue'

// 3. Props/Emits 定义
const props = defineProps(/* ... */)
const emit = defineEmits(/* ... */)

// 4. 组合式函数调用
const router = useRouter()
const { data, fetch } = useFetch()

// 5. 响应式状态
const count = ref(0)
const form = reactive({/* ... */})

// 6. 计算属性
const doubleCount = computed(() => count.value * 2)

// 7. 方法函数
const handleSubmit = async () => {
  // ...
}

// 8. 生命周期/侦听器
onMounted(() => {
  fetch()
})

// 9. 暴露给父组件的方法
defineExpose({
  submit: handleSubmit
})
</script>

3. 为复杂组件使用组合式函数

当组件逻辑复杂时,将其拆分为组合式函数:

javascript 复制代码
<script setup>
// 将复杂逻辑抽离
import { useUserManagement } from './composables/useUserManagement'
import { useFormValidation } from './composables/useFormValidation'

const {
  users,
  loading,
  error,
  fetchUsers,
  deleteUser
} = useUserManagement()

const {
  form,
  errors,
  validate,
  resetForm
} = useFormValidation()
</script>

总结

<script setup> 是 Vue 3 的重大改进,它:

主要优势:

  • 代码更简洁:减少样板代码,提高开发效率

  • 类型支持更好:与 TypeScript 完美集成

  • 自动暴露:无需手动返回模板使用的变量

  • 组件自动注册:导入即用

  • 更符合现代 JS 习惯:像写普通 JavaScript 一样写 Vue


需要注意:

  • 学习新的 API:definePropsdefineEmitsdefineExpose

  • 需要了解响应式保持(使用 toRefs

  • 部分 Options API 概念需要转换思维


适用场景:

  • 所有新的 Vue 3 项目

  • 使用 Composition API 的组件

  • 需要良好 TypeScript 支持的项目

  • 希望减少样板代码,提高开发效率


<script setup> 代表了 Vue 组件编写的未来方向,是 Vue 3 开发的首选方式。虽然一开始需要适应新的语法,但一旦习惯,你会发现自己再也回不去传统的写法了!

相关推荐
qx092 小时前
html中使用vue3+elementplus
javascript·vue.js·html
科技D人生2 小时前
Vue.js 学习总结(19)—— Vue3 按钮防重复点击三种方案总结
前端·vue.js·uniapp·vue3 防重复提交·uniapp 防重复提交·前端防抖
麦麦大数据3 小时前
F064 vue+flask知识图谱在线学习系统
vue.js·flask·知识图谱·在线学习·学习系统·ai学伴·ai助学
登山人在路上3 小时前
Vuex构建可维护的 Vue.js 状态管理
vue.js
登山人在路上3 小时前
Pinia :下一代 Vue 状态管理
vue.js
闲蛋小超人笑嘻嘻4 小时前
非父子通信: provide和inject
前端·javascript·vue.js
AllinLin4 小时前
JS中的call apply bind全面解析
前端·javascript·vue.js
海绵宝龙4 小时前
Vue 中的 Diff 算法
前端·vue.js·算法
zhougl9964 小时前
vue中App.vue和index.html冲突问题
javascript·vue.js·html