Composition API VS Options API:谁才是你的最佳选择?

作为一名 Vue 开发者,你可能也曾纠结过:到底应该使用 Composition API 还是继续使用 Options API 呢? 毕竟,Vue 3 已经发布了,Composition API 也成为了官方推荐的写法。

我的答案和 Vue 官方文档的说法一致:

除非你的项目非常小或者简单,否则请使用 Composition API 而不是 Options API。

为什么这么说呢?这篇文章会带你深入了解 Composition API 和 Options API 的区别,帮助你做出明智的选择。

主要区别:Composition API 更胜一筹!

我会重点介绍三个最重要的区别:

  1. 代码组织: 代码应该如何组织才能更清晰易懂?
  2. 模块化: 如何将代码拆分成独立的功能模块?
  3. 可重用性: 如何让代码可以重复使用,避免重复编写?

1. 代码组织:告别混乱,拥抱清晰

代码组织对开发至关重要,想象一下,你在一堆杂乱无章的代码中寻找某个功能,简直是噩梦!

Options API 强制你把所有计算属性放在一起,所有监听器放在一起,所有方法放在一起等等。但这并不是我们写代码的习惯!

我们通常根据功能来修改代码,从一个计算属性跳到一个方法,再跳到模板,然后又回到生命周期钩子。我们不会花一个小时写完所有计算属性,然后再去写所有方法。

所以,我们希望代码能够根据功能来组织,而不是按照 Options API 强制的结构。

Composition API 就非常擅长这一点! 它可以让你根据功能来组织代码,而不是按照 Options API 强制的结构。

当然,也有一点需要注意: Composition API 允许你自由组织代码,但也意味着你需要自己确保代码整洁。

程序员嘛,偶尔会偷懒,对吧? Options API 至少能保证代码有一点组织性。 但只要你稍微自律一点,就可以在 Composition API 中保持代码整洁。 而且,我们可以通过创建可组合函数来解决这个问题,后面会详细讲解。

2. 模块化:代码整洁,功能独立

除了组织代码,我们还需要将代码拆分成独立的功能模块,就像把一个大房子分成不同的房间,这样才能更好地管理和维护。

Composition API 可以轻松地创建模块,我们称之为 "可组合函数"。

当你把相关功能的代码放在一起,并进行良好的组织之后,就可以轻松地将它们提取到可组合函数中。这些可组合函数可以是单一用途的,也可以是可重复使用的。

这样,代码就变得更加易读和易懂了。

以下是一个包含"点赞"按钮逻辑的组件示例:

javascript 复制代码
import { ref, computed, onMounted } from 'vue';

const likes = ref(0);

onMounted(() => {
  fetch('/api/post/1')
    .then((response) => response.json())
    .then((data) => {
      likes.value = data.post.likes;
    });
});

const sendLike = async () => {
  likes.value++;

  fetch('/api/post/1/likes', {
    method: 'POST'
  })
    .catch((error) => {
      likes.value--;
    });
};

const likesAmount = computed(() => {
  return likes.value + ' people have liked this';
});

现在,我们将相关代码提取到 useLikes 可组合函数中:

javascript 复制代码
// useLikes.js

import { ref, computed, onMounted } from 'vue';

export default function useLikes(postId) {
  const likes = ref(0);

  onMounted(() => {
    fetch(`/api/posts/${postId}`)
      .then((response) => response.json())
      .then((data) => {
        likes.value = data.post.likes;
      });
  });

  const sendLike = async () => {
    likes.value++;

    fetch('/api/post/1/likes', {
      method: 'POST'
    })
      .catch((error) => {
        likes.value--;
      });
  }

  return {
    likes,
    sendLike
  }
}

现在,我们的组件代码变得更加简洁:

javascript 复制代码
import { computed } from 'vue';
import useLikes from '@/useLikes';

const {
  likes,
  sendLike,
} = useLikes(1);

const likesAmount = computed(() => {
  return likes.value + ' people have liked this';
});

你并不需要总是将可组合函数提取到单独的文件中,你也可以在组件中内联定义它们。如果你想了解更多关于可组合函数的信息,可以查看 Clean Components Toolkit 中的 21 种 Vue 设计模式。

Options API 没有很好的方法可以像这样将逻辑分解成模块。

过去,我们尝试模块化 Vue 逻辑的三种主要方法是:

  • 纯 JavaScript 函数和对象
  • 无渲染组件
  • 混合

它们都有自己的缺陷,所以让我们仔细看看。

纯 JavaScript 函数和对象 在 Vue 项目中模块化逻辑当然很有用。这里的主要缺点是,你无法访问 Vue 的任何反应性功能,这极大地限制了你可以实际使用它来做什么。

可组合函数本质上是 JavaScript 函数,但它们具有能够访问 Vue 反应性系统的额外优势(如果它不使用 Vue 的反应性,它只是一个实用程序方法)。

无渲染组件 曾经非常流行,因为它们是模块化 Vue 2 逻辑的最佳方法------在 Composition API 出现之前。但你实际上是在创建一个完整的组件来承载一些逻辑,因此存在很多不必要的开销。

我记得是 TailwindCSS 的 Adam Wathan 在 2018 年首次推广无渲染组件。

还存在从插槽作用域内部访问所需变量的问题:

vue 复制代码
<template>
  <RenderlessComponent v-slot="slotProps">
    <ChildComponent :props="slotProps" />
  </RenderlessComponent>
</template>

如果你在组件中存在需要使用 slotProps 中的值的逻辑,那么你就没有办法了。 要么将该逻辑推送到 ChildComponent 中,要么使用这种模板技巧,在模板中调用一个方法并将 slotProps 传递给它:

vue 复制代码
<template>
  <RenderlessComponent v-slot="slotProps">
    <ChildComponent :props="myMethod(slotProps)" />
  </RenderlessComponent>
</template>
javascript 复制代码
const myMethod = slotProps => {
  // 现在我们可以在这个方法中访问 slotProps,但这只是个方法内的操作 :/
};

无渲染组件仍然有一些用途,但说实话,不要再做各种模板体操了------即使这很有趣。

最后,我们来说说混合 (mixins)。

早在 2016 年,web 开发社区就已开始远离混合,这完全是合理的。

主要原因是混合会创建隐式的依赖关系,非常难以追踪。方法、计算属性和其他内容以一种不清晰且难以发现的方式添加到组件中。

当你尝试更改使用混合的组件(或更改组件中使用的混合)时,你真的无法知道会发生什么,或者你可能会破坏什么。

但是,使用可组合函数,你清楚地知道它们被用在了哪些组件中,以及如何在这些组件中使用它们。每个依赖关系都是显式的,所以你知道所有逻辑来自哪里。

在 Nuxt 3 中,自动导入依然如此。虽然导入不是显式的,但可组合函数本身的使用是显式的,这正是我们需要的。

混合的问题实际上是一个著名的被称为脆弱基类问题的问题。 真是太可惜了,我们之前没有预料到这一点。

3. 可重用性:让代码更灵活,更高效

最后,我们来说说可重用性。 写完并测试好一段代码后,我们希望能够重复使用它,充分发挥它的价值。

我们已经看到,使用 Composition API,我们可以使用可组合函数来创建高度可重用的逻辑块。这些可组合函数可以与 Vue 的各种反应性系统、生命周期钩子以及几乎所有 Vue 的流程进行交互。

但由于它们没有通过 this 绑定到组件实例,因此我们可以在整个项目中甚至多个项目之间共享它们。

VueUse 就是一个很好的例子。

它是一个包含数十个经过测试的、非常有用且写得很好的可组合函数的库,它们经过精心设计,确保稳定可靠。如果你还没有尝试过 VueUse,以下是一些示例:

  • useTitle 可以轻松更新页面的标题:
javascript 复制代码
const title = useTitle('The Initial Title');

// 反应式地更改标题
title.value = 'This is a new title';
  • useAsyncState 允许你异步执行代码,而不会阻塞主代码,并在异步代码返回后反应式更新:
javascript 复制代码
const { state: number, isLoading } = useAsyncState(fetchData());

// 当数据被获取后,由于 `number` 是反应式更新的,因此此值会自动重新计算
const computedValue = computed(() => number + 4);
  • useStorage 允许你反应式地访问 localStorage、sessionStorage 或你想要的任何其他存储提供程序:
javascript 复制代码
// 从存储中读取数据,并提供一个回退值
const name = useStorage('my-name', 'Default Name');

// 任何更改都会与 localStorage 反应式地同步
name.value = 'Michael Thiessen';

但是,如果你尝试使用 Options API 来实现类似的功能,那么你就没有办法了。

我们已经讲过为什么无渲染组件和混合不是很好的选择,那么你就真的没有其他选择了。你可以使用纯 JavaScript 文件,但再次提醒你,你将错过所有反应性功能。

而且,说实话,我们谈论的是让 Vue 组件和逻辑可重用,所以如果没有反应性,你真的走不了多远。

这三个主要因素------代码组织、模块化和可重用性------是相互关联的。你需要模块化来实现可重用性,而模块化需要良好的组织。

虽然 Options API 可以提供部分功能,但 Composition API 在这方面真正脱颖而出,这也是官方推荐使用 Composition API 的主要原因。

更多区别

虽然代码组织、模块化和可重用性是最重要的考虑因素,但我想指出一些其他区别。

在本节中,我们将介绍以下内容:

  1. 表现力
  2. this 带来的问题
  3. TypeScript 支持
  4. 样板代码
  5. 处理常量(和外部资源)

1. 表现力

Composition API 比 Options API 更加细粒度,让你可以更精确地控制反应性的工作方式。

实际上,在 Vue 3 中,Options API 是使用 Composition API 构建的。它是一个抽象层,帮助简化了一些关键区域,但这意味着你必须牺牲一些控制力和灵活性

有时这很好,但有时你想要额外的灵活性,这就是 Composition API 真正闪耀的地方。

2. this 是个大麻烦

Options API 始终需要访问 this。

如果你使用 Options API 有一段时间了,你可能也会遇到一些问题。

例如,你需要确保使用常规函数,否则你将无法访问 this:

javascript 复制代码
methods: {
  someMethod: () => {
    // 糟糕,"this 未定义"
    console.log(this.someComputedProp);
  },
  someMethod() {
    // 好多了
    console.log(this.someComputedProp);
  }
}

但有时,你需要使用箭头函数而不是常规函数,才能在函数上下文中保留 this:

javascript 复制代码
methods: {
  someMethod() {
    const sortedArray = this.array.sort((a, b) => {
      // 需要使用箭头函数才能使用 `this`
      return b[this.sortProp] - a[this.sortProp];
    });
    // ...
  }
}

在 Composition API 中,这些烦恼都不存在了------ 你可以根据需要编写函数,它会正常工作。

你无需考虑或担心如何访问 this。

3. TypeScript 支持

Vue 3 是用 TypeScript 编写的,所以与 Vue 2 相比,TypeScript 支持有了很大改进。然而,与 Options API 相比,使用 Composition API 仍然可以更好地控制类型。

使用 TS 与 Options API 需要将你的组件包装在 defineComponent 方法中,然后还需要做一些额外的工作才能正确地进行类型化。

而使用 Composition API,我们得到了对 TypeScript 惊人的支持,并且在每个新版本中不断改进。例如,泛型组件:

javascript 复制代码
<script setup lang="ts" generic="T">
  defineProps<{
    items: T[]
    selected: T
  }>()
</script>

generic 字段中的值与普通 TypeScript 中的 <...> 一样,所以我们在这里有完全的灵活性:

javascript 复制代码
<script setup lang="ts" generic="T extends string | number, U extends Item">

import type { Item } from './types'

defineProps<{

 id: T

 list: U[]

}>()

</script>

如果你正在用 TypeScript 编写应用,那么 Composition API 绝对是首选。

4. 样板代码

我相信你一定见过 Composition API 和 Options API 在样板代码方面的对比。

使用 <script setup> 时,这种区别会更加明显,因为我们只需要写更少的代码:

javascript 复制代码
<script setup>

import { ref } from 'vue';

const number = ref(0);

</script>
javascript 复制代码
<script>

export default {

 data() {

 return {

 number: 0,

 };

 }

};

</script>

这个例子很简单,但我们减少了很多行代码。在更大的组件中,节省的代码量会更多。

根据你编写的组件不同,这些样板代码可能会成为文件的重要组成部分。但主要问题是,所有这些额外的代码都是噪音,在阅读和理解代码时,你必须过滤掉它们。

更少的样板代码让代码更容易理解和使用!

5. 处理常量和外部资源

如果需要在模板中渲染一个图标或 SVG 文件,该怎么办?

使用 Options API 时,你必须决定是将其作为反应式值放在 data 中,还是作为计算属性:

javascript 复制代码
import Logo from './logo.svg';

export default {
  data() {
    return {
      logo: Logo,
    };
  },
};

但 SVG 图标并不真正属于"数据",它也不需要是反应式的。所以,也许你可以通过计算属性将其放入模板作用域:

javascript 复制代码
import Logo from './logo.svg';

export default {
  computed: {
    // 使用箭头函数,因为我们不访问 `this`
    logo: () => Logo,
  },
};

虽然这两种方法都有效,但它们都没有什么道理。

如果你真的想避免使其成为反应式的,还有第三种方法:

javascript 复制代码
import Logo from './logo.svg';

export default {
  data() {
    // 将其添加到组件实例中,而不使其成为反应式的
    this.logo = Logo;

    return {
      // ...
    };
  },
};

通过直接将该值添加到组件实例中,你可以在模板中访问它,但它不会成为反应式的。

以下是用

javascript 复制代码
import Logo from './logo.svg';

是的,这就是我们真正需要做的,因为 <script setup> 作用域内的任何内容都在模板作用域内。

你认为哪种方法更有道理?

Options API 的优势

如果我说 Options API 在某一方面比 Composition API 更好,我可没说谎:

简单性。

是的,Composition API 更强大,更灵活,而且在很多方面都更好。

但它更复杂。它可能更难理解。

我认为这是它最大的缺点,也是许多开发者不喜欢它的主要原因,也许是唯一的原因。我们很多从 Vue 2 开始使用 Options API 的开发者,多年来已经非常熟练,而 Composition API 是全新的,并且可能仍然很困难。

你无需处理在不同地方使用 .value 和 ref 的不一致性,你无需考虑如何组织代码------ 你只需要写代码,然后事情就会发生。

然而,在我看来,这些额外的难度与 Composition API 带来的额外价值相比,是微不足道的。

只需要花一些时间来习惯它。

为什么 Composition API 比 Options API 更好?

我完全同意 Vue 官方立场,认为 Composition API 是更好的选择。

主要原因是:

  • 更好的代码组织
  • 更好的模块化
  • 通过可组合函数实现更好的可重用性

Composition API 在一些不太重要的方面也更出色:

  • 更强的表现力
  • 无需处理 this 和作用域的复杂性
  • 更好的 TypeScript 支持
  • 更少的样板代码
  • 处理常量和外部资源更容易

虽然这些区别在小型项目中也会显现出来,但随着应用程序规模和复杂程度的增加,这些区别会变得更加显著和重要。这就是文档专门为生产应用程序推荐 Composition API 的原因。

但是,Composition API 有一个主要的缺点:它的复杂性。

但是,我和大多数 Vue 社区成员都认为,这种微不足道的复杂性增加,完全值得它带来的所有好处。

只需要花一些时间和精力来适应它。

相关推荐
加班是不可能的,除非双倍日工资13 分钟前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi1 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip1 小时前
vite和webpack打包结构控制
前端·javascript
excel2 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国2 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼2 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy2 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
草梅友仁2 小时前
草梅 Auth 1.4.0 发布与 ESLint v9 更新 | 2025 年第 33 周草梅周报
vue.js·github·nuxt.js
ZXT2 小时前
promise & async await总结
前端
Jerry说前后端2 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化