1、初识组合式API:为什么要有组合式API?它解决了OptionsAPI的哪些痛点?setup函数的基本用法。

说实话,我刚接触Vue 3的组合式API时,心里是有点抵触的。毕竟Options API用了好几年,写起来也挺顺手。但真正在几个中大型项目中用下来,我不得不承认——组合式API确实解决了很多我过去「忍了很久」的问题。

今天我们就来聊聊,为什么Vue团队要搞出这么一套新东西。它到底解决了什么痛点?以及那个神秘的setup函数,到底该怎么用。

Options API的三大痛点

先别急着学新东西,我们得先搞清楚:旧的东西到底哪里不好?

我在2018年开始用Vue 2做项目,那时候觉得Options API真香。但随着项目越来越大,问题就慢慢浮出水面了。

痛点一:逻辑分散,维护困难

你想想看,一个功能相关的代码,被拆散在datamethodscomputedwatch里。比如一个搜索功能:

  • data里定义searchQuerysearchResults
  • methods里写handleSearch
  • computed里写filteredResults
  • watch里监听searchQuery变化

一个功能,四个地方。代码一多,你就要在文件里上下翻飞。我记得有一次改一个表单验证逻辑,在methods里改了函数,又去data里加字段,再到watch里加监听——来回跳了七八次,差点把自己绕晕。

核心问题:关注点分离变成了「选项分离」。同一个关注点的代码,被强行拆到不同选项中。

痛点二:逻辑复用困难

Vue 2的mixin,说白了就是个「命名空间污染器」。我在一个项目里用过三个mixin,结果有个变量名冲突了,查了半天才发现是两个mixin里定义了同名变量。更坑的是,mixin的来源不清晰,你根本不知道某个方法是从哪个mixin来的。

后来我改用高阶组件(HOC)和插槽(slot)来复用逻辑,但写起来又太啰嗦。一个简单的逻辑复用,要包好几层组件,代码可读性直线下降。

痛点三:TypeScript支持不友好

这个我深有体会。Options API里用TypeScript,你得写一堆装饰器,或者用vue-class-component。但说实话,那玩意儿写起来跟Java似的,一点都不「Vue」。而且类型推导经常出问题,你明明定义了类型,编辑器就是不认。

我曾经踩过的坑:用Options API + TypeScript写一个复杂表单,this的类型推导经常丢失,最后不得不写一堆as断言。代码丑不说,还容易出bug。

组合式API如何解决这些痛点?

好,痛点说完了。我们来看看组合式API是怎么「对症下药」的。

解决方案一:逻辑聚合

组合式API允许你按功能组织代码。同一个功能的所有逻辑,可以写在一起:

// 组合式API:搜索功能的所有逻辑在一起
import { ref, computed, watch } from 'vue'

export function useSearch() {
  const searchQuery = ref('')
  const searchResults = ref([])
  
  const filteredResults = computed(() => {
    return searchResults.value.filter(item => 
      item.includes(searchQuery.value)
    )
  })
  
  watch(searchQuery, (newVal) => {
    // 执行搜索
    fetchResults(newVal)
  })
  
  function handleSearch(query) {
    searchQuery.value = query
  }
  
  return {
    searchQuery,
    searchResults,
    filteredResults,
    handleSearch
  }
}

你看,搜索相关的所有东西——状态、计算属性、监听器、方法——全在一个函数里。想改搜索逻辑?不用满文件找了,直接看这个函数就行。

解决方案二:轻松复用

组合式API的复用,说白了就是「函数调用」。没有命名冲突,来源清晰:

// 在组件中使用
import { useSearch } from './composables/useSearch'
import { usePagination } from './composables/usePagination'

export default {
  setup() {
    const search = useSearch()
    const pagination = usePagination()
    
    return {
      ...search,
      ...pagination
    }
  }
}

每个组合函数(composable)都是一个独立的模块。你一眼就能看出searchQuery来自useSearchcurrentPage来自usePagination。清爽得很。

解决方案三:TypeScript友好

组合式API天然支持TypeScript。因为就是普通的函数调用,类型推导非常顺畅:

import { ref } from 'vue'

// 类型自动推导
const count = ref(0) // Ref<number>

// 显式指定类型
const user = ref<User | null>(null)

// 函数参数类型
function useCounter(initialValue: number) {
  const count = ref(initialValue)
  // ...
}

没有装饰器,没有奇怪的语法。就是TypeScript本身的能力,Vue只是顺势而为。

setup函数:组合式API的入口

好,现在我们来聊聊setup函数。它是组合式API的「大门」,所有组合式API的代码都在这里写。

基本用法

import { ref, onMounted } from 'vue'

export default {
  setup() {
    // 定义响应式数据
    const count = ref(0)
    
    // 定义方法
    function increment() {
      count.value++
    }
    
    // 生命周期钩子
    onMounted(() => {
      console.log('组件挂载了')
    })
    
    // 暴露给模板
    return {
      count,
      increment
    }
  }
}
小提示:setup中,ref创建的响应式数据,在JavaScript中要使用.value来访问。但在模板中,Vue会自动解包,直接写count就行。

setup的执行时机

setup在组件创建之前执行,这时候组件实例还没完全创建好。所以:

  • 你不能在setup里用this——它指向undefined
  • setup里拿不到datacomputedmethods里的东西
  • propscontext可以作为参数传入
export default {
  props: {
    title: String
  },
  setup(props, context) {
    // props 是响应式的
    console.log(props.title)
    
    // context 包含 attrs, slots, emit
    context.emit('update', '新值')
    
    // 也可以解构
    const { attrs, slots, emit } = context
  }
}
注意:不要解构props!因为解构后就不是响应式的了。如果你需要单独使用某个prop,可以用toRefs来转换。

setup的返回值

setup返回什么,模板就能用什么:

setup() {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  
  function add() {
    count.value++
  }
  
  // 返回对象
  return {
    count,
    double,
    add
  }
}

也可以返回一个渲染函数,但这种情况比较少:

setup() {
  return () => h('div', 'Hello World')
}

从Options API到组合式API:一个简单对比

功能 Options API 组合式API
响应式数据 data() ref() / reactive()
计算属性 computed computed()
方法 methods 普通函数
监听器 watch watch()
生命周期 mounted() onMounted()
逻辑复用 mixin / HOC 组合函数

嗯,看到这个对比,你应该能感受到组合式API的「函数式」风格了吧?说白了,就是把原来分散在各个选项里的东西,统一成函数调用的形式。

我的建议

如果你刚开始接触组合式API,别急着把所有代码都重构一遍。我个人习惯是:新功能用组合式API写,旧功能慢慢迁移。这样压力小,也能在实践中慢慢体会它的好处。

我记得第一次用组合式API重构一个搜索组件时,代码从200行减少到150行,而且逻辑清晰了很多。当时我就觉得——嗯,这玩意儿确实香。

下一章,我们会深入refreactive,看看Vue 3的响应式系统到底是怎么工作的。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。