Composition API常见问题解答
提示
此常见问题解答假定您具备Vue经验 - 尤其是在主要使用Options API的情况下使用Vue 2的经验。
什么是Composition API?
Composition API是一组API,允许我们使用导入的函数而不是声明选项来编写Vue组件。这是一个总称,包括以下API
响应式API,例如
ref()
和reactive()
,允许我们直接创建响应式状态、计算状态和监视器。生命周期钩子,例如
onMounted()
和onUnmounted()
,允许我们以编程方式挂钩到组件生命周期。依赖注入,即
provide()
和inject()
,允许我们在使用响应式API的同时利用Vue的依赖注入系统。
Composition API是Vue 3和Vue 2.7的内置功能。对于较老的Vue 2版本,请使用官方维护的@vue/composition-api
插件。在Vue 3中,它也主要用于与单文件组件中的<script setup>
语法一起使用。以下是一个使用Composition API的组件的基本示例
vue
<script setup>
import { ref, onMounted } from 'vue'
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
尽管基于函数组合的API风格,但Composition API不是函数式编程。Composition API基于Vue的可变、细粒度的响应式范式,而函数式编程强调不可变性。
如果您想了解如何使用Composition API与Vue结合,您可以通过左侧侧边栏顶部的切换将网站级别的API偏好设置为Composition API,然后从头开始浏览指南。
为什么使用Composition API?
更好的逻辑复用
Composition API 的主要优势是它能够以可组合函数的形式实现干净的、高效的逻辑复用,详情请参阅 可组合函数。它解决了 混入(mixins)的所有缺点,混入是 Options API 的主要逻辑复用机制。
Composition API 的逻辑复用能力催生了令人印象深刻的社区项目,如 VueUse,这是一个不断增长的组合实用工具集合。它还提供了一种干净的机制,可以轻松地将具有状态的第三方服务或库集成到 Vue 的响应性系统中,例如 不可变数据、状态机 和 RxJS。
更灵活的代码组织
许多用户喜欢使用 Options API 默认编写的有组织代码:每个部分都有其位置,基于其所属的选项。然而,当单个组件的逻辑超过一定复杂性阈值时,Options API 会带来严重的限制。这种限制在需要处理多个 逻辑关注点 的组件中尤为突出,我们在许多生产 Vue 2 应用中亲身体验过这一点。
以 Vue CLI 的 GUI 中的文件夹浏览器组件为例:该组件负责以下逻辑关注点
- 跟踪当前文件夹状态并显示其内容
- 处理文件夹导航(打开、关闭、刷新...)
- 处理新文件夹创建
- 切换仅显示收藏夹文件夹
- 切换显示隐藏文件夹
- 处理当前工作目录变更
该组件的 原始版本 是用 Options API 编写的。如果我们根据每行代码处理的逻辑关注点为其着色,这将是这样
请注意,处理相同逻辑关注点的代码被迫在不同的选项下分裂,位于文件的不同部分。在一个数百行长的组件中,理解和导航单个逻辑关注点需要不断上下滚动文件,这比应该要困难得多。此外,如果我们打算将逻辑关注点提取为可重用工具,则需要相当多的工作来查找和从文件的不同部分提取正确的代码片段。
这是相同的组件,在 重构为 Composition API 之前和之后
请注意,现在与相同逻辑关注点相关的代码可以分组在一起:我们在处理特定逻辑关注点时不再需要在不同选项块之间跳转。此外,我们现在可以轻松地将一组代码移动到外部文件,因为我们不再需要重新整理代码来提取它们。这种减少重构摩擦是大型代码库长期维护的关键。
更好的类型推断
近年来,越来越多的前端开发者开始采用TypeScript,因为它有助于我们编写更健壮的代码,更有信心地进行更改,并提供了IDE支持的卓越开发体验。然而,最初于2013年构思的Options API,在设计时并未考虑到类型推断。我们不得不实施一些荒谬复杂的类型体操,以使类型推断与Options API协同工作。即便付出了所有这些努力,Options API的类型推断对于混合和依赖注入仍然可能会失败。
这导致许多希望使用Vue和TS的开发者倾向于使用由vue-class-component
支持的Class API。然而,基于类的API严重依赖ES装饰器,这是一个在2019年Vue 3开发期间仅处于2阶段建议的语言特性。我们认为将官方API建立在不稳定建议之上风险太高。从那时起,装饰器建议已经经历了又一轮全面修订,并于2022年最终达到3阶段。此外,基于类的API还受到与Options API类似的逻辑复用和组织限制。
相比之下,Composition API主要使用普通变量和函数,它们天然适合类型。使用Composition API编写的代码可以享受完整的类型推断,几乎不需要手动类型提示。大多数情况下,Composition API的代码在TypeScript和普通JavaScript中看起来几乎相同。这也使得普通JavaScript用户能够从中受益于部分类型推断。
更小的生产包和更少的开销
使用Composition API和<script setup>
编写的代码也比Options API等效代码更高效、更易于压缩。这是因为<script setup>
组件中的模板被编译为与<script setup>
代码相同作用域内的内联函数。与从this
属性访问不同,编译后的模板代码可以直接访问<script setup>
内部声明的变量,而不需要实例代理。这也导致更好的压缩,因为所有变量名都可以安全地缩短。
与Options API的关系
权衡
一些从Options API迁移到Composition API的用户发现他们的Composition API代码组织性较差,并得出结论认为Composition API在代码组织方面“更差”。我们建议有这种观点的用户从不同角度看待这个问题。
确实,Composition API不再提供引导您将代码放入相应桶中的“护栏”。作为交换,您可以像编写普通JavaScript一样编写组件代码。这意味着您可以在编写普通JavaScript时像对待Composition API代码一样应用任何代码组织最佳实践。如果您能编写有组织的JavaScript,您也应该能够编写有组织的Composition API代码。
选项API确实允许你在编写组件代码时“思考得更少”,这也是许多用户喜爱它的原因。然而,在减少心理负担的同时,它也把你锁定在规定的代码组织模式中,没有逃脱的出口,这在大型项目中可能会使代码重构或提高代码质量变得困难。在这方面,组合API提供了更好的长期可扩展性。
组合API覆盖了所有用例吗?
是的,在状态逻辑方面。当使用组合API时,只有少数选项可能仍然需要:props
、emits
、name
和inheritAttrs
。
提示
从3.3版本开始,你可以在<script setup>
中直接使用defineOptions
来设置组件名称或inheritAttrs
属性
如果你打算仅使用组合API(以及上面列出的选项),你可以通过一个编译时标志来减少生产包的大小,该标志从Vue中删除了选项API相关的代码。注意这也会影响你的依赖项中的Vue组件。
我可以在同一个组件中使用这两个API吗?
是的。你可以在选项API组件中使用setup()
选项来使用组合API。
然而,我们只建议这样做,如果你有一个需要与使用组合API编写的新功能/外部库集成的现有选项API代码库。
选项API会被弃用吗?
不会,我们没有这样的计划。选项API是Vue的一个组成部分,这也是许多开发者喜欢它的原因。我们也意识到,组合API的许多好处只有在大型项目中才会显现,而选项API仍然是许多低到中等复杂场景的一个可靠选择。
与类API的关系
鉴于组合API提供了与TypeScript的出色集成以及额外的逻辑复用和代码组织优势,我们不再建议在Vue 3中使用类API。
与React Hooks的比较
组合API提供了与React Hooks相同级别的逻辑组合能力,但有一些重要差异。
React Hooks会在组件每次更新时重复调用。这产生了一些可能导致经验丰富的React开发者困惑的注意事项。它还导致了可能导致严重影响开发体验的性能优化问题。以下是一些例子
Hooks对调用顺序敏感,不能有条件地调用。
在React组件中声明的变量可能会被hook闭包捕获,如果开发者未能传递正确的依赖数组,则可能会变成“过时”。这导致React开发者依赖于ESLint规则来确保传递了正确的依赖项。然而,该规则通常不够智能,过度补偿了正确性,导致不必要的无效化和在遇到边缘情况时的头痛。
昂贵的计算需要使用
useMemo
,这又需要手动传递正确的依赖数组。将事件处理器传递给子组件默认会导致不必要的子组件更新,需要显式使用
useCallback
进行优化。这几乎是必需的,并且还需要正确设置依赖数组。忽视这一点将导致应用程序默认过度渲染,并且可能在不经意间引发性能问题。陈旧的闭包问题,结合并发功能,使得难以推断钩子代码何时运行,并且在使用应该跨渲染持续存在(通过
useRef
)的可变状态时变得繁琐。
注意:上述与记忆化相关的一些问题可以通过即将推出的 React 编译器 解决。
相比之下,Vue 组合式API
仅调用一次
setup()
或<script setup>
代码。这使得代码更好地与惯用JavaScript的使用直觉相匹配,因为无需担心陈旧的闭包。组合式API调用也不受调用顺序的影响,并且可以进行条件调用。Vue的运行时响应式系统会自动收集用于计算属性和观察者的响应式依赖项,因此无需手动声明依赖项。
无需手动缓存回调函数以避免不必要的子组件更新。一般来说,Vue的精细粒度响应式系统确保子组件只在需要时更新。对于Vue开发者来说,手动优化子组件更新通常不是问题。
我们承认React Hooks的创造性,它是组合式API的主要灵感来源。然而,上述提到的问题确实存在于其设计中,我们注意到Vue的响应式模型恰好提供了一种绕过这些问题的方法。