1、初识组合式API:为什么要有组合式API?它解决了OptionsAPI的哪些痛点?setup函数的基本用法。
说实话,我刚接触Vue 3的组合式API时,心里是有点抵触的。毕竟Options API用了好几年,写起来也挺顺手。但真正在几个中大型项目中用下来,我不得不承认——组合式API确实解决了很多我过去「忍了很久」的问题。
今天我们就来聊聊,为什么Vue团队要搞出这么一套新东西。它到底解决了什么痛点?以及那个神秘的setup函数,到底该怎么用。
Options API的三大痛点
先别急着学新东西,我们得先搞清楚:旧的东西到底哪里不好?
我在2018年开始用Vue 2做项目,那时候觉得Options API真香。但随着项目越来越大,问题就慢慢浮出水面了。
痛点一:逻辑分散,维护困难
你想想看,一个功能相关的代码,被拆散在data、methods、computed、watch里。比如一个搜索功能:
data里定义searchQuery、searchResultsmethods里写handleSearchcomputed里写filteredResultswatch里监听searchQuery变化
一个功能,四个地方。代码一多,你就要在文件里上下翻飞。我记得有一次改一个表单验证逻辑,在methods里改了函数,又去data里加字段,再到watch里加监听——来回跳了七八次,差点把自己绕晕。
痛点二:逻辑复用困难
Vue 2的mixin,说白了就是个「命名空间污染器」。我在一个项目里用过三个mixin,结果有个变量名冲突了,查了半天才发现是两个mixin里定义了同名变量。更坑的是,mixin的来源不清晰,你根本不知道某个方法是从哪个mixin来的。
后来我改用高阶组件(HOC)和插槽(slot)来复用逻辑,但写起来又太啰嗦。一个简单的逻辑复用,要包好几层组件,代码可读性直线下降。
痛点三:TypeScript支持不友好
这个我深有体会。Options API里用TypeScript,你得写一堆装饰器,或者用vue-class-component。但说实话,那玩意儿写起来跟Java似的,一点都不「Vue」。而且类型推导经常出问题,你明明定义了类型,编辑器就是不认。
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来自useSearch,currentPage来自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里拿不到data、computed、methods里的东西- 但
props和context可以作为参数传入
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行,而且逻辑清晰了很多。当时我就觉得——嗯,这玩意儿确实香。
下一章,我们会深入ref和reactive,看看Vue 3的响应式系统到底是怎么工作的。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。