以前用Vue2的时候,写组件基本就是按data、computed、methods、watch来划分。小页面这么写确实清楚,但页面一大,同一个功能的东西可能散落在好几个地方,改一个功能要在文件里上下翻。

而Vue3的Composition API,说白了就是让你可以按功能来组织代码,而不是按API类型。

为什么会想换写法?

拿一个用户列表页举例,通常会有:

  • 用户数据
  • 加载中状态
  • 分页
  • 搜索关键词
  • 请求报错处理

如果用Options API,大概会写成这样:

export default {
  data() {
    return {
      users: [],
      loading: false,
      keyword: ''
    }
  },

  computed: {
    filteredUsers() {
      // ...
    }
  },

  methods: {
    async loadUsers() {
      // ...
    }
  },

  watch: {
    keyword() {
      // ...
    }
  }
}

逻辑没错,但如果你要改搜索相关的逻辑,可能得在data、watch、computed之间来回切。此组件的业务内容再多一点,维护起来相当难受。

但Composition API可以把相关的东西放在一块:

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

const users = ref([])
const keyword = ref('')

const filteredUsers = computed(() => {
  return users.value.filter(user =>
    user.name.includes(keyword.value)
  )
})
</script>

至少,相关变量和计算逻辑挨在一起,读起来顺畅一些。

ref和reactive怎么选

ref 和 reactive 是Vue3里最常用的两个响应式API。

基本类型我一般直接用ref:

const count = ref(0)
count.value++

对象可以用reactive:

const user = reactive({
  name: 'Tom',
  age: 18
})

user.age++

不过在实际项目中,我慢慢倾向于统一用ref,尤其是用到ts的项目。比如:

const user = ref<User | null>(null)

这种写法在表示“数据还没加载”的时候比reactive自然很多,不用额外处理空对象的情况。

computed用来处理计算出来的数据

我一般不太喜欢在模板里写太复杂的表达式,比如:

<div>
  {{ users.filter(user => user.enabled).length }}
</div>

逻辑简单还好,一旦条件多了,刚写完就看不懂了(doge

我将其抽到computed中实现:

const enabledUsers = computed(() => {
  return users.value.filter(user => user.enabled)
})

模板里只留干净的展示:

<div>
  {{ enabledUsers.length }}
</div>

何不乐哉。

watch不是用来算值的

watch确实好用,但我觉得它挺容易被用过头。

比如监听关键词变化去重新请求数据,这没问题:

watch(keyword, () => {
  loadUsers()
})

但如果是拿一个状态去算另一个状态,我一般会优先考虑computed。

举个例子,不推荐这样写:

const firstName = ref('')
const lastName = ref('')
const fullName = ref('')

watch(
  [firstName, lastName],
  () => {
    fullName.value = `${firstName.value} ${lastName.value}`
  }
)

直接用computed更简单:

const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`
})

我自己的判断标准:如果能从已有数据推导出来,就computed;如果需要触发异步操作或者修改外部状态,再 watch。

最大的好处其实是逻辑复用

说实话,<script setup>确实让写法更简洁,但我觉得Composition API真正值钱的地方,是它让逻辑抽离变得特别自然(不知是否属于我用惯了CA的错误感)。

比如分页逻辑,我经常抽成一个独立函数:

export function usePagination() {
  const page = ref(1)
  const pageSize = ref(20)
  const total = ref(0)

  const nextPage = () => {
    if (page.value * pageSize.value < total.value) {
      page.value++
    }
  }

  return {
    page,
    pageSize,
    total,
    nextPage
  }
}

组件里直接用:

const {
  page,
  pageSize,
  total,
  nextPage
} = usePagination()

这样分页逻辑就跟具体页面解耦了,换一个页面也能复用。

类似的,还可以抽:

  • useRequest
  • useSearch
  • useLocalStorage
  • usePermission
  • useTheme

项目越大,这种组织方式就越能减少重复代码。

TypeScript 配合起来挺顺的

Vue3对TypeScript的支持也是我比较在意的一点。

比如定义用户类型:

interface User {
  id: number
  name: string
  email: string
}

然后:

const users = ref<User[]>([])
const currentUser = ref<User | null>(null)

IDE 能直接提示出 users.value[0].name,如果后端接口字段变了,TypeScript也会在编译阶段报出来,省了不少调试时间。

小结一下

用了Composition API一段时间之后,我最大的感受其实不是代码量少了多少,而是:

写组件的时候,更多是在按功能模块组织代码,而不是按Vue的API类型来分块。

小组件可能区别不大,但一旦页面复杂度上来,比如要处理请求、缓存、分页、搜索、权限、状态管理这些,Composition API的维护优势就会慢慢体现出来。

这也是我现在在Vue3项目里基本都会优先选它的原因。

最后修改:2026 年 09 月 02 日
如果觉得我的文章对你有用,请随意赞赏