为什么你应该使用 Vue 的新 Composition API
你经常听说Vue 中的Composition API。但它可能有点吓人,而且你也不太清楚它为什么这么好用。
在本文中,你将通过比较旧方法和新方法,确切地了解为什么你应该学习使用它。示例也从简单开始,然后逐渐复杂,因此你可以看到 Composition API 与你习惯的并没有什么不同。
这将取代 Vue 2 当前的选项 API,但好消息是,您不需要在Vue 3 应用程序中使用它。您仍然可以使用久经考验的选项 API,并像以前在 Vue 2 中一样编写组件。对于那些想要立即采用这种新方法或只是想熟悉更新的用户,这里有一些使用 Vue 3 的组合 API 重写的常见简单组件的示例。
一个简单的计数器
计数器组件几乎是前端框架的“Hello World”必备组件。让我们看看它在 Vue 2 中是什么样子的:
<template>
<div class="counter">
<span>{{ counter }}</span>
<button @click="counter += 1">+1</button>
<button @click="counter -= 1">-1</button>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0
}
}
}
</script>
我们显示一个 span 标签,其中包含一个计数器数据对象,该对象从零开始。然后我们有两个按钮,v-on:click
它们带有属性和内联代码,用于指示计数器加一或减一。然后在 script 标签中,我们通过 data 方法返回的对象初始化该计数器。
现在让我们看看 Vue 3 中同一个组件是什么样子的:
<template>
<span>{{ counter }}</span>
<button @click="counter += 1">+1</button>
<button @click="counter -= 1">-1</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const counter = ref(0);
return {
counter
};
}
}
</script>
您可能首先注意到的是,我从模板中移除了那个包装器 div。以前在 Vue 中,如果您尝试在模板标签下渲染包含多个顶级元素的组件,就会出现错误。在 Vue 3 中,这种情况不再存在!
接下来是脚本部分,它比上一个组件稍微长一点。不过,这在意料之中,因为我们的功能是最低限度的,而且使用 Composition API 的设置也稍微多一些。让我们逐行回顾一下这些变化。
import { ref } from 'vue';
该ref
方法是为了在组合 API 中赋予任何数据点响应性而setup
必需的。默认情况下,该方法返回的变量不具有响应性。
export default {
setup() { ... }
}
接下来,我们有了 newsetup
方法。它是所有组合 API 组件的入口点,它返回的对象中的任何内容都将暴露给组件的其余部分。这包括计算属性、数据对象、方法和组件生命周期钩子等。
setup() {
const counter = ref(0);
return {
counter
};
}
我们首先使用前面提到的ref
方法创建一个计数器,并传入初始值零。然后,我们要做的就是返回这个计数器,并将其包装在一个对象中。
从现在开始,我们的组件就像以前一样工作,显示当前值并允许用户根据给定的按钮按下情况进行调整!让我们继续,看看一些有更多活动部件的东西。
购物车
更复杂一点,我们将创建一个使用 Vue 中两个常见属性的组件:计算属性和定义方法。我认为一个很好的例子是一个基本的购物车组件,它可以显示用户在类似电商网站上选择的商品。
以下是 Vue 2 中使用选项 API 的示例:
<template>
<div class="cart">
<div class="row" v-for="(item, index) in items">
<span>{{ item.name }}</span>
<span>{{ item.quantity }}</span>
<span>{{ item.price * item.quantity }}</span>
<button @click="removeItem(index)">Remove</button>
</div>
<div class="row">
<h3>Total: <span>{{ cartTotal }}</span></h3>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
name: "Cool Gadget",
quantity: 3,
price: 19.99
},
{
name: "Mechanical Keyboard",
quantity: 1,
price: 129.99
}
]
}
},
methods: {
removeItem(index) {
this.items.splice(index, 1);
}
},
computed: {
cartTotal() {
return this.items.reduce((total, item) => {
return total += (item.price * item.quantity);
}, 0);
}
}
}
</script>
购物车中的商品以 列出v-for
,每个商品后都有一个按钮,点击后可将其从主数组中移除。购物车的总金额通过一个计算属性计算得出,reduce
其值显示在商品底部。我觉得这很简单!
让我们看看使用组合 API 在 Vue 3 中具有这些属性的类似组件是什么样子:
<template>
<div class="cart">
<div class="row" v-for="(item, index) in items">
<span>{{ item.name }}</span>
<span>{{ item.quantity }}</span>
<span>{{ item.price * item.quantity }}</span>
<button @click="removeItem(index)">Remove</button>
</div>
<div class="row">
<h3>Total: <span>{{ cartTotal }}</span></h3>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const items = ref([
{
name: "Cool Gadget",
quantity: 3,
price: 19.99
},
{
name: "Mechanical Keyboard",
quantity: 1,
price: 129.99
}
]);
const removeItem = (index) => {
items.value.splice(index, 1);
};
const cartTotal = computed(() => {
return items.value.reduce((total, item) => {
return total += (item.price * item.quantity);
}, 0);
});
return {
items,
removeItem,
cartTotal
};
}
}
</script>
最大的区别是计算属性和方法不在根 Vue 对象自己的属性中,而是只是在主setup()
方法中定义和返回的普通方法。
对于方法,我们只需将它们创建为函数:
const removeItem = (index) => {
items.value.splice(index, 1);
};
只要我们将它们包含在返回的对象中,它们就会暴露给组件的其余部分(并可供其使用)。计算属性几乎完全相同,只是它被包装在computed
从主 Vue 包导入的方法中:
const cartTotal = computed(() => {
return items.value.reduce((total, item) => {
return total += (item.price * item.quantity);
}, 0);
});
这样,我们可以解耦组件的各个部分,并将它们进一步拆分成可复用并导入到其他多个组件的功能部分。我们将在下一个示例中看到如何做到这一点。
例如,如果我们愿意,我们可以轻松地将cartTotal
计算属性或方法拆分到它们自己的文件removeItem
中。然后,我们不需要在上面的主组件中定义和使用它们,而是导入它们并调用指定的方法。
进入最后一个组件!
点赞按钮
我们的第三个也是最后一个示例比前两个示例更加复杂,让我们看看一个组件是什么样子,它必须从 API 端点提取数据并对用户输入做出反应。
Vue 2 应用程序中的选项 API 可能如下所示:
<template>
<button @click="sendLike" :disabled="isDisabled">{{ likesAmount }}</button>
</template>
<script>
export default {
data() {
return {
likes: 0,
isDisabled: false
}
},
mounted() {
fetch('/api/post/1')
.then((response) => response.json())
.then((data) => {
this.likes = data.post.likes;
});
},
methods: {
sendLike() {
this.isDisabled = true;
this.likes++;
fetch('/api/post/1/likes', {
method: 'POST'
})
.then((response) => {
this.isDisabled = false;
}
.catch((error) => {
this.likes--;
this.isDisabled = false;
});
}
},
computed: {
likesAmount() {
return this.likes + ' people have liked this';
}
}
}
</script>
比我们前面的例子稍微复杂一点,但让我们分解一下。
我们从模板中开始,按钮绑定v-on:click
到sendLike
方法,并将 disabled 属性绑定到 data 属性isDisabled
。按钮内部通过 data 属性显示点赞数likes
。
继续执行脚本,我们将返回的数据对象初始化为 0 likes
,并将isDisabled
其设置为 false。我们使用mounted()
生命周期方法调用 API 端点,并将点赞数量设置为特定帖子的点赞数。
然后我们定义一个sendLike
方法,禁用按钮并将点赞数增加 1。(我们在实际发送请求之前增加点赞数,以便立即记录我们的用户交互。)
最后,我们将请求发送到我们虚构的 API,并等待响应。无论哪种方式,我们都会从按钮中删除 disabled 属性,但如果服务器由于某种原因返回错误,我们会删除记录的初始 like 并将其重置likes
为先前的值。
现在,让我们看看使用组合 API 在 Vue 3 中类似的组件是什么样子:
<template>
<button @click="sendLike" :disabled="isDisabled">{{ likesAmount }}</button>
</template>
<script>
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const likes = ref(0);
const isDisabled = ref(false);
onMounted(() => {
fetch('/api/post/1')
.then((response) => response.json())
.then((data) => {
likes = data.post.likes;
});
});
const sendLike = async () => {
isDisabled.value = true;
likes.value++;
fetch('/api/post/1/likes', {
method: 'POST'
})
.then((response) => {
isDisabled.value = false;
})
.catch((error) => {
likes.value--;
isDisabled.value = false;
});
}
const likesAmount = computed(() => {
return likes.value + ' people have liked this';
});
return {
likes,
isDisabled,
likesAmount,
sendLike
};
}
}
</script>
好的,就是这样!
现在,它与我们的计数器组件之间的主要区别在于增加了一个已安装的生命周期钩子。它不像 Vue 2 的 options API 那样,需要另一个单独的方法,而是再次以 中的函数形式编写setup
,并包装在包含的onMounted()
方法中。
这正是 Composition API 开始在可组合组件中大放异彩的地方。这个点赞按钮组件有点长,它包含一些可以拆分成单独文件并导入的功能。
例如,我们可能希望在不同的组件中包含对喜欢的检索和更新,因此我们可以创建一个新的 JavaScript 文件来处理该操作:
// useLikes.js
import { ref, computed, onMounted } from 'vue';
export default function useLikes(postId) {
const likes = ref(0);
const likesAmount = computed(() => {
return likes + ' people have liked this'
});
onMounted(() => {
fetch(`/api/posts/${postId}`)
.then((response) => response.json())
.then((data) => {
likes.value = data.post.likes;
});
});
return {
likes,
likesAmount
}
}
这个无渲染组件useLikes
初始化了占位符点赞数量 0。然后,它向传入 ID 的帖子的 API 端点发送一个获取请求。完成后,我们的点赞将更新以匹配当前帖子的内容。
那么,在我们的主组件中如何使用它呢?像这样:
<template>
<button @click="sendLike" :disabled="isDisabled">{{ likesAmount }}</button>
</template>
<script>
import { useLikes } from '@/useLikes';
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const {
likes,
likesAmount
} = useLikes(1);
const isDisabled = ref(false);
const sendLike = async () => {
isDisabled.value = true;
likes.value++;
fetch('/api/post/1/likes', {
method: 'POST'
})
.then((response) => {
isDisabled.value = false;
})
.catch((error) => {
likes.value--;
isDisabled.value = false;
});
}
return {
likes,
isDisabled,
likesAmount,
sendLike
};
}
}
</script>
首先,我们使用 import 语句获取导出useLikes
函数,然后使用一个由likes和likesAmount ref 对象及其方法组成的解构对象。它们通过同一个函数被引入到主组件中useLikes
。
剩下要做的就是传递postId
属性,我们已将其设置为硬编码值 1。
总结
好了,就是这样!您已经看到了在 Vue 2 中创建的三个不同组件,以及它们在 Vue 3 中复制的对应组件。
无论您是经验丰富的框架开发人员,还是仍在学习的新手,我希望这些内容能够帮助您顺利掌握 Vue 的最新版本。尽管组合 API 的外观与众不同,有时甚至令人望而生畏,但它可以帮助您以更稳定、更易于维护的方式组织和重构前端代码。
如果您有任何问题、意见或想进一步讨论有关
Web 开发的一般问题,请随时通过Twitter或以下讨论与我们联系。