在本教程中,我们将探索如何使用 Vue 3 组合API及其最新的代码可重用性功能。
代码共享和可重用性是软件开发的基石之一。从早期的编程开始,代码重复的问题就让程序员发明了保持代码干燥、可重用和可移植的策略。随着时间的推移,这些策略不断被打磨和完善,新的策略也在不断发展。
这同样适用于Vue以及其他编程语言和框架。随着 Vue 框架的发展,它继续提供更好的可重用性方法。
什么是组合 API 以及创建它的原因
让我们考虑一下是什么让一段代码可重用。对我来说,可重用性有三个主要原则:
- 代码抽象。当一段代码可以适应多个不同的用例(如许多编程语言中的类)时,它就是抽象的。
- 代码可移植性。当一段代码不仅可以在一个项目的不同地方使用,而且可以在不同的项目中使用时,它就是可移植的。
- 代码解耦(或松耦合)。当更改一个不需要更改另一个时,一段代码与另一段代码解耦。它们尽可能地相互独立。当然,完全解耦是不可能的——这就是为什么开发人员使用的更准确的术语是“松散耦合”。
Composition API 是一种用于构建和构建 Vue 3 组件的新策略。它结合了上述所有三个原则,并允许创建抽象的、可移植的和松散耦合的组件,这些组件可以在不同的项目之间重用和共享。
将 Vue 组合 API 添加到框架的动机
将 Composition API 添加到 Vue 3 的动机是明确而简单的:生成更紧凑和碎片整理的代码。让我们进一步探讨一下。
当我第一次发现 Vue 时,我就被它的 Options (object-based) API 迷住了。在我看来,与 Angular 和 React 等价物相比,它更加清晰和优雅。一切都有自己的位置,我可以把它放在那里。当我有一些数据时,我把它放在一个data选项中;当我有一些功能时,我将它们放在一个methods选项中,依此类推:
// Options API example
export default {
props: ['title', 'message'],
data() {
return {
width: 30,
height: 40
}
},
computed: {
rectArea() {
return this.width * this.height
},
},
methods: {
displayMessage () {
console.log(`${this.title}: ${this.message}`)
}
}
}
所有这些看起来都很有序、干净、易于阅读和理解。然而,事实证明,这仅在应用程序相对较小且简单时才有效。随着应用程序及其组件越来越多,代码碎片和无序增加。
当在大型项目中使用 Options API 时,代码库很快就开始变得像一个碎片化的硬盘。一个组件中代码的不同部分,在逻辑上属于一起,分布在不同的地方。这使得代码难以阅读、理解和维护。
这就是 Composition API 发挥作用的地方。它提供了一种按顺序构建代码的方法,其中所有逻辑部分作为一个单元组合在一起。在某种程度上,您可以将 Composition API 想象成一个磁盘碎片整理工具。它可以帮助您保持代码紧凑和干净。
这是一个简化的视觉示例:

如您所见,使用 Options API 构建的组件代码可能非常碎片化,而使用 Composition API 构建的组件代码按功能分组,看起来更易于阅读和维护。
Vue 组合 API 优势
下面总结了 Composition API 提供的主要优势:
- 更好的代码组合。
- 逻辑相关的块保持在一起。
- 与 Vue 2 相比,整体性能更好。
- 更干净的代码。代码在逻辑上更有序,这使得它更有意义,更易于阅读和理解。
- 易于提取和导入功能。
- TypeScript 支持,可改进 IDE 集成和代码辅助以及代码调试。(这不是 Composition API 的特性,但值得一提的是 Vue 3 的特性。)
组合 API 基础
尽管组合 API 具有强大的功能和灵活性,但它非常简单。要在组件中使用它,我们需要添加一个setup()函数,这实际上只是添加到 Options API 中的另一个选项:
export default {
setup() {
// Composition API
}
}
在setup()函数内部,我们可以创建反应变量和操作它们的函数。然后我们可以返回那些我们希望在组件的其余部分中可用的变量和/或函数。要制作反应性变量,您需要使用反应性 API函数(ref()、reactive()、computed()等)。要了解更多关于它们的用法,您可以浏览这个关于 Vue 3 Reacivity 系统的综合教程。
该setup()函数接受两个参数:props和context。
props是响应式的,并且会在传入新的 props 时更新:
export default {
props: ["message"],
setup(props) {
console.log(props.message)
}
}
如果你想解构你的道具,你可以通过toRefs()在setup()函数内部使用来做到这一点。如果您改用 ES6 解构,它将删除props 反应性:
import { toRefs } from 'vue'
export default {
props: ["message"],
setup(props) {
// const { message } = props <-- ES6 destructuring. The 'message' is NOT reactive now.
const { message } = toRefs(props) // Using 'toRefs()' keeps reactivity.
console.log(message.value)
}
}
Context是一个普通的 JavaScript 对象(非响应式),它公开了其他有用的值,如attrs, slots, emit. 这些是Options API 中的$attrs、$slots和等价物。$emit
该setup()函数在组件实例创建之前执行。因此,您将无法访问以下组件选项:data、computed、methods和模板引用。
在setup()函数中,您可以使用on前缀访问组件的生命周期挂钩。例如,mounted将成为onMounted. 生命周期函数接受一个回调,该回调将在组件调用钩子时执行:
export default {
props: ["message"],
setup(props) {
onMounted(() => {
console.log(`Message: ${props.message}`)
})
}
}
注意:您不需要显式调用beforeCreateandcreated钩子,因为该setup()函数本身执行类似的工作。在setup()函数中,this不是对当前活动实例的引用,因为setup()在解析其他组件选项之前调用。
比较 Options API 和 Composition API
让我们快速比较一下 Options 和 Composition API。
首先,这是一个简单的待办事项应用程序组件,使用 Options API 构建,具有添加和删除任务的功能:
<template>
<div id="app">
<h4> {{ name }}'s To Do List </h4>
<div>
<input v-model="newItemText" v-on:keyup.enter="addNewTodo" />
<button v-on:click="addNewTodo">Add</button>
<button v-on:click="removeTodo">Remove</button>
<transition-group name="list" tag="ol">
<li v-for="task in tasks" v-bind:key="task" >{{ task }}</li>
</transition-group>
</div>
</div>
</template>
<script>
export default {
data() {
return {
name: "Ivaylo",
tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
newItemText: ""
}},
methods: {
addNewTodo() {
if (this.newItemText != "") {
this.tasks.unshift(this.newItemText);
}
this.newItemText = "";
},
removeTodo() {
this.tasks.shift();
},
}
};
</script>
为了简洁起见,我在这里省略了 CSS 代码,因为它不相关。您可以在Vue 2 选项 API 示例中查看完整代码。
如您所见,这是一个非常简单的示例。我们有三个数据变量和两个方法。让我们看看如何使用 Composition API 重写它们:
<script>
import { ref, readonly } from "vue"
export default {
setup () {
const name = ref("Ivaylo")
const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
const newItemText = ref("")
const addNewTodo = () => {
if (newItemText.value != "") {
tasks.value.unshift(newItemText.value);
}
newItemText.value = "";
}
const removeTodo = () => {
tasks.value.shift();
}
return {
name: readonly(name),
tasks: readonly(tasks),
newItemText,
addNewTodo,
removeTodo
}
}
};
</script>
正如您在这个 Vue 3 组合 API 示例中看到的,功能是相同的,但所有数据变量和方法都在setup()函数内移动。
为了重新创建三个数据反应变量,我们使用该ref()函数。然后,我们重新创建addNewTodo()andremoveTodo()函数。请注意,所有的使用this都被删除,而是直接使用变量名,后跟value属性。所以不是this.newItemText我们写newItemText.value,等等。最后,我们返回变量和函数,以便它们可以在组件的模板中使用。请注意,当我们在模板中使用它们时,我们不需要使用该value属性,因为所有返回的值都是自动浅展开的。所以我们不需要更改模板中的任何内容。
我们将name和tasks设为只读以防止它们在组件之外进行任何更改。在这种情况下,tasks只能通过addNewTodo()和更改属性removeTodo()。
组合 API 何时适合组件,何时不适合
仅仅因为创造了一些新技术并不意味着你需要或必须使用它。在决定是否使用新技术之前,你应该考虑一下你是否真的需要它。尽管组合 API 提供了一些很大的好处,但在小型和简单的项目中使用它可能会导致不必要的复杂性。原理与Vuex用法相同:对于小型项目来说可能太复杂了。
例如,如果您的组件大多是单一功能的——也就是说,它们只做一件事——您不需要使用 Composition API 添加不必要的认知负荷。但是,如果你注意到你的组件变得越来越复杂和多功能——它们处理多个单一的任务和/或你的应用程序的许多地方都需要它们的功能——那么你应该考虑使用 Composition API。在具有大量复杂、多功能组件的大中型项目中,Composition API 将帮助您生成高度可重用和可维护的代码,而无需进行不必要的修改或变通方法。
因此,您可以将以下规则作为一般建议:
- Options API最适合构建小型、简单、单一功能的组件,这些组件的功能需要低可重用性。
- 组合 API最适合构建更大、更复杂的多功能组件,这些组件的功能需要更高的可重用性。
什么是 Vue 可组合组件?
组合 API的秘密武器是能够创建高度可重用的模块,称为组合。它们允许我们提取反应状态和功能,并在其他组件中重用它。Composables 相当于 Options API 中的 mixin。它们也可以被认为是 React hooks的等价物。
在可组合之前,有三种方法可以在组件之间重用和共享代码:实用函数、mixin 和无渲染组件。但是可组合物击败了它们。让我们看看为什么。
实用功能
实用函数很有用但有限,因为它们无法处理 Vue 特定的功能,如反应状态。这是一个例子:
// utils.js
export function increment(count) {
return count++;
}
...
在这里,我们有一个increment(count)实用函数,可以将 count 变量加一。但是我们不能在这里定义反应状态。我们需要count在消费组件中添加一个反应变量,如下所示:
// Counter.vue
<template>
<p>{{ count }}</p>
<button v-on:click="increment(count)">Increment</button>
</template>
import { increment } from './utils.js'
export default {
data() {
return { count: 0 }
}
}
无渲染组件
无渲染组件(即不渲染任何 HTML 模板,仅渲染状态和功能的组件)比实用函数要好一些,因为它们可以处理 Vue 特定的功能,但它们的灵活性也有限。这是一个例子:
// RenderlessCounter.vue
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
},
render() {
return this.$slots.default({
count: this.count,
increment: this.increment
});
}
这里稍微好一点,因为我们可以定义反应状态并在作用域插槽的帮助下导出它。当我们实现组件时,我们使用定义的变量和方法来构建自定义模板:countincrement()
// Counter.vue
<renderless-counter>
<template v-slot:default="{count, increment}">
<p>{{ count }}</p>
<button v-on:click="increment">Increment</button>
</template>
</renderless-counter>
混合
Mixin 是使用 Options API 构建的组件之间代码共享的官方方式。mixin 只是一个导出的选项对象:
// CounterMixin.js
export default {
data() {
return { count: 0 }
},
methods: {
increment() {
this.count++
}
}
}
我们可以导入 mixin 的 options 对象并像它的成员属于消费组件的 options 对象一样使用它:
// Counter.vue
<template>
<p>{{ count }}</p>
<button v-on:click="increment">Increment</button>
</template>
import CounterMixin from './CounterMixin'
export default {
mixins: [CounterMixin]
}
如果组件已经定义了一些选项(data、methods、computed等),它们将与来自导入的 mixin(s) 的选项合并。我们很快就会看到,这种行为有一些严重的缺点。
与可组合类相比,Mixins 有一些严重的缺点:
- 数据源被遮挡。当一个组件的数据来自多个 mixin 时,我们无法确定哪些属性来自哪个 mixin。使用全局注册的 mixin 时也是如此。
- 可重用性受限。Mixins 不接受参数,所以我们不能添加额外的逻辑。
- 名称冲突。如果两个或多个 mixin 具有相同名称的属性,则将使用最后一个 mixin 的属性,这可能不是我们想要的。
- 没有数据保护。我们不能确定 mixin 的属性不会被消费组件改变。
Vue 可组合的好处
作为本节的总结,让我们总结一下 Vue 3 可组合的主要优点:
- 数据源是透明的。要使用可组合物,我们需要导入它们并使用解构来提取所需的数据。所以我们可以清楚地看到每个属性/方法的来源。
- 没有名称冲突。我们可以使用来自多个可组合项的同名属性,只需重命名它们即可。
- 数据受到保护。我们可以将返回的属性设为只读,从而限制来自其他组件的突变。原理与 Vuex 中的 mutation 相同。
- 共享状态。通常,组件中使用的每个可组合项都会创建一个新的本地状态。但是我们也可以定义全局状态,这样当可组合项在不同的组件中使用时,它们将共享相同的状态。
创建和使用 Vue 组合
在本节中,我们将学习如何创建和使用自定义 Vue 3 组合。
注意:对于这个项目,你需要在你的机器上安装Node和Vue CLI 。
让我们使用 Vue CLI创建一个新的 Vue 3 项目:
vue create vue-composition-api-examples
当您被要求选择一个预设时,请确保您选择了默认的 Vue 3 选项。
您可以在Vue Composition API 示例 repo中找到所有项目文件。
创建一个数据获取可组合
在下面的示例中,我们将创建一个可用于各种扫描仪的自定义数据获取组合。
首先,创建一个src/composables文件夹并向其中添加一个useFetch.js文件。这是该文件的代码:
import {toRefs, ref, reactive} from 'vue';
export function useFetch(url, options) {
const data = ref(null);
const state = reactive({
error: null,
loading: false
});
const fetchData = async () => {
state.loading = true;
try {
const res = await fetch(url, options);
data.value = await res.json();
} catch (e) {
state.error = e;
} finally {
state.loading = false;
}
};
fetchData();
return {data, ...toRefs(state)};
}
从技术上讲,可组合只是我们导出的一个函数(useFetch()在我们的例子中)。在该函数中,我们创建data和state变量。然后我们创建一个fetchData()函数,在其中我们使用Fetch API从特定源获取数据并将结果分配给data属性。在fetchData()函数之后,我们立即调用它,以便将获取的数据分配给变量。最后,我们返回所有变量。我们toRefs()在这里使用正确提取error和loading变量,保持它们的反应性。
伟大的!现在,让我们看看如何在组件中使用我们的可组合。
在src/components文件夹中,添加一个UserList.vue包含以下内容的文件:
<template>
<div v-if="error">
<h2>Error: {{ error }}</h2>
</div>
<div v-if="loading">
<h2>Loading data...</h2>
</div>
<h2>Users</h2>
<ul v-for="item in data" :key="item.id">
<li><b>Name:</b> {{ item.name }} </li>
<li><b>Username:</b> {{ item.username}} </li>
</ul>
</template>
<script>
import { useFetch } from '../composables/useFetch.js';
export default {
setup() {
const {data, error, loading} = useFetch(
'https://jsonplaceholder.typicode.com/users',
{}
);
return {
data,
error,
loading
};
}
};
</script>
<style scoped>
ul {
list-style-type: none;
}
</style>
在这里,我们导入useFetch()可组合对象,然后在setup()函数中提取其变量。在我们返回变量后,我们可以在模板中使用它们来创建用户列表。在模板中,我们使用v-if指令来检查 and 的真实性error,loading如果其中一个为真,则显示相应的消息。然后,我们使用v-for指令和data属性来创建实际的用户列表。
我们需要做的最后一件事是在App.vue文件中添加组件。打开App.vue文件并将其内容替换为以下内容:
<template>
<div id="app">
<user-list />
</div>
</template>
<script>
import UserList from "./components/UserList";
export default {
name: "App",
components: {
UserList
}
};
</script>
就是这样。这是创建和使用可组合的基础。但让我们更进一步,让用户列表组件更加灵活和可重用。
创建一个高度可重用的组件
重命名UserList.vue并UniversalList.vue用以下内容替换其内容:
<template>
<div v-if="error">
<h2>Error: {{ error }}</h2>
</div>
<div v-if="loading">
<h2>Loading data...</h2>
</div>
<slot :data="data"></slot>
</template>
<script>
import { useFetch } from '../composables/useFetch.js';
export default {
props: ['url'],
setup(props) {
const {data, error, loading} = useFetch(
props.url,
{}
);
return {
data,
error,
loading
};
}
};
</script>
这里有两个重要的变化。首先,当我们调用 时useFetch(),不是显式添加 URL,而是用urlprop 替换它。这样,我们可以根据需要使用不同的 URL。data其次,我们添加了一个 slot 组件并提供了作为它的 prop ,而不是列表的预制模板。这样,我们可以在实现组件时使用我们需要的任何模板。让我们看看如何在实践中做到这一点。
将 的内容替换为App.vue以下内容:
<template>
<div id="app">
<universal-list url="https://jsonplaceholder.typicode.com/todos" v-slot="{ data }">
<h2>Todos</h2>
<ol>
<li v-for="item in data" :key="item.id"> {{ item.title }} - {{ item.completed }} </li>
</ol>
</universal-list>
</div>
</template>
<script>
import UniversalList from "./components/UniversalList";
export default {
name: "App",
components: {
UniversalList
}
};
</script>
现在,当我们包含通用列表组件时,我们可以根据需要提供自定义模板。我们添加所需的 URL 并使用该v-slot指令从useFetch()可组合对象中获取数据。最后,我们按照我们的意愿构造获取的数据。在我们的例子中,它是一个待办事项列表。
为了清楚起见,这些示例已被简化,但它们有效地展示了创建和使用可组合组件以及构建可重用组件的主要原则。一旦掌握了基础知识,您就可以继续学习其他有关组件可重用性的小技巧和窍门,并不断改进您现在正在构建和/或之前构建的内容。
结论
在计划和讨论 Composition API 时,许多人认为这是错误的方法。幸运的是,许多其他人看到了这种功能的潜力。我希望本教程也对您有所帮助。Composables 解决了 mixins 和实用程序函数的许多问题,并提供了一种使我们的代码更可重用、更紧凑和更清洁的好方法。对我来说,Composition API 与 Reactivity API 和插槽相结合,形成了可重用性的三位一体。

如若转载,请注明出处:https://www.vsaren.org/2724.html