Vue 3 Composition API 与 <script setup>

系列导航:Vue 3 现代开发指南
上一篇:Vue 3 现代工程搭建
下一篇:Vue 3 响应式基础

Composition API 的核心价值不是“代码更新”,而是把同一业务能力相关的状态、派生值、副作用和操作组织在一起,并将它们抽成可复用的组合式函数。Options API 在 Vue 3 中仍然受支持;小组件使用哪一种都可以,但新建 TypeScript 项目通常优先采用 <script setup>

一、两种 API 的组织方式

Options API 按选项类型组织代码:

<script lang="ts">
export default {
  data() {
    return { count: 0 }
  },
  computed: {
    doubled(): number {
      return this.count * 2
    },
  },
  methods: {
    increment() {
      this.count++
    },
  },
}
</script>

Composition API 按功能组织代码:

<script setup lang="ts">
import { computed, ref } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)

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

后者在功能复杂、需要复用逻辑或类型较多时更容易维护。它并不意味着所有代码都要塞进一个巨大 setup();逻辑变长时,应抽成 useXxx() 组合式函数。

二、普通 setup() 做了什么

理解底层形式有助于读旧代码:

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'CounterPanel',
  setup() {
    const count = ref(0)

    function increment() {
      count.value++
    }

    return { count, increment }
  },
})
</script>

setup() 返回的属性可以在模板中使用。在 setup() 里不要依赖组件实例 this;Composition API 通过导入函数和显式参数访问能力。

<script setup> 是编译时语法糖,它自动把顶层绑定暴露给模板:

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

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

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

这不是运行时把所有变量挂到组件实例上,而是编译器直接生成对应代码,因此更简洁,也有更好的类型推断。

三、组件名怎么处理

组件通常会根据 .vue 文件名自动推断名称。例如 UserProfile.vue 会得到 UserProfile 名称,所以大多数文件不需要额外声明。

确实需要显式选项时,使用内置宏 defineOptions()

<script setup lang="ts">
defineOptions({
  name: 'UserProfile',
  inheritAttrs: false,
})
</script>

Vue 3.3 起已经内置 defineOptions(),不需要 vite-plugin-vue-setup-extend,也不应为了写组件名再增加一个普通 <script> 块。

四、编译器宏不需要导入

以下常用 API 是 <script setup> 编译器宏:

  • defineProps()
  • defineEmits()
  • defineModel()
  • defineExpose()
  • defineOptions()
  • defineSlots()
  • withDefaults()

它们只在 SFC 编译阶段存在,不要写:

// 不需要,也不应该这样导入
import { defineProps, defineEmits } from 'vue'

refcomputedwatchonMountedprovide 等是运行时 API,必须从 vue 导入。

五、一个完整的 TypeScript 组件

下面的搜索面板同时展示 Props、Emits、响应式状态和派生值:

<script setup lang="ts">
import { computed, ref } from 'vue'

interface Props {
  initialQuery?: string
  minLength?: number
}

const { initialQuery = '', minLength = 2 } = defineProps<Props>()

const emit = defineEmits<{
  search: [query: string]
  clear: []
}>()

const query = ref(initialQuery)
const normalizedQuery = computed(() => query.value.trim())
const canSubmit = computed(
  () => normalizedQuery.value.length >= minLength,
)

function submit() {
  if (canSubmit.value) {
    emit('search', normalizedQuery.value)
  }
}

function clear() {
  query.value = ''
  emit('clear')
}
</script>

<template>
  <form @submit.prevent="submit">
    <input v-model="query" type="search" aria-label="搜索关键词" />
    <button type="submit" :disabled="!canSubmit">搜索</button>
    <button type="button" @click="clear">清空</button>
  </form>
</template>

Vue 3.5 中,从 defineProps() 解构得到的变量会保持响应式。默认值也可以直接写在解构表达式中;这比旧教程一律使用 withDefaults() 更自然。

六、什么时候抽组合式函数

当一段逻辑满足以下任一条件时,可以抽成 useXxx()

  • 被两个以上组件复用;
  • 同时包含状态、计算属性、监听和清理逻辑;
  • 组件已经难以一眼看出 UI 事件和业务流程;
  • 逻辑可以独立测试。

例如:

// composables/useCounter.ts
import { computed, ref } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubled = computed(() => count.value * 2)

  function increment(step = 1) {
    count.value += step
  }

  return { count, doubled, increment }
}

组件中直接使用:

<script setup lang="ts">
import { useCounter } from '@/composables/useCounter'

const { count, doubled, increment } = useCounter(1)
</script>

<template>
  <p>{{ count }} × 2 = {{ doubled }}</p>
  <button type="button" @click="increment()">加一</button>
</template>

组合式函数以 use 开头是生态约定,不是 Vue 的强制语法。

七、常见误区

1. 把 Composition API 当成必须重写的理由

Options API 没有被弃用。稳定的旧组件不必为了“现代化”全部重写。优先在新增功能、难复用逻辑和类型问题明显的区域渐进迁移。

2. 在模板里写过多业务逻辑

模板适合声明视图。长条件、数组处理和多步表达式应放进 computed 或函数中。

3. 滥用响应式

不会影响模板或其他副作用的数据用普通 const 即可。并非所有变量都需要 ref()

4. 一个组合式函数做所有事情

usePage() 如果同时管理请求、权限、弹窗、表格和埋点,只是把大组件搬到了大函数里。组合式函数仍应有清晰、单一的职责。

小结

<script setup> 是当前 Vue 3 + TypeScript 项目的默认表达方式。组件名通常由文件名推断,编译器宏无需导入;功能变复杂时,用组合式函数按业务能力拆分,而不是按 datamethodswatch 的类别拆分。

官方资料

系列导航:目录 · 上一篇 · 下一篇