Vue.js 异步请求模式:使用无渲染组件
内容
想法的起源
Vue 组件中的 HTTP 请求:一个典型示例
异步无渲染组件
大多数 Vue 应用程序都需要异步 HTTP 请求,并且有很多方法可以实现它们:在mounted()
生命周期钩子中、在按钮触发的方法中、在商店中(使用vuex时)或在asyncData()
和fetch()
方法中(使用Nuxt)。
虽然使用 axios 发出简单请求非常容易,但我们通常希望涵盖至少两个附加状态:
- 在请求等待期间向用户显示一些内容
- 优雅地处理错误
处理这些状态会增加额外的代码,并且在必须实现许多不同的请求时会很快导致代码重复。
内容
要直接切入正题,请跳至异步无渲染组件。
注意:本例中使用 Axios 发出 HTTP 请求,但它与其他任何 AJAX 请求库配合使用效果同样出色。此外,本例还使用了非常棒的免费 Dog API:https://dog.ceo/dog-api/ 🐶。
想法的起源
这个想法不是我自己的,而是借鉴了 Vue.js 创建者 Evan You @youyuxi的想法,他在第 81 集的Full Stack Radio Podcast上与 Adam Whatan 讨论高级 Vue 组件时再次表达了这个想法。
Vue 组件中的 HTTP 请求:一个典型示例
让我们从一个简单的示例开始,请求一张随机的狗狗图片。该mounted()
钩子包含一个 axios 调用,用于填充image
变量。
Vue.component("example", {
el: "#example",
data() {
return {
image: null
};
},
mounted() {
axios
.get("https://dog.ceo/api/breeds/image/random")
.then(function(response) {
this.image = response.data;
});
}
});
很简单。但是,我们想要显示加载动画并处理请求错误。因此,除了image
变量pending: false
和之外,error: null
还添加了 。该mounted()
钩子如下所示:
Vue.component("example", {
[...]
mounted() {
this.pending = true;
axios
.get("https://dog.ceo/api/breeds/image/random")
.then(function(response) { this.image = response.data })
.catch(function(error) { this.error = error })
.finally(function () { this.pending = false });
}
});
现在,可以为 显示加载指示器pending === true
,并在 时显示基本错误消息error !== null
。这非常简单,但重复实现待处理/成功/错误行为可能会很繁琐。此外,如果请求包含用户可以更改的参数(例如过滤器或排序选项),则请求必须转移到一个方法,每当参数发生变化时,都必须调用该方法来重新加载数据。
抽象出这种简单行为并使其可重复使用的一个简单有效的方法是......
异步无渲染组件
此组件利用了功能极其丰富的作用域插槽 (Scoped Slot) 功能。插槽是任何可以传递给组件的 HTML 片段,用于告诉组件:“这里,在某个地方渲染它”。有了作用域插槽,接收 HTML 片段的组件会回复:“太棒了,我会把你的 HTML 放在这里。如果你愿意,这里有一些数据,你可以用在片段中。”
Async Renderless 组件就是这样一个组件,它接收一段 HTML、一个 URL 和一些参数并回答:“嘿,看,我正在为你请求这些数据,这是data
,pending
供error
你使用。”
完整的异步无渲染组件:
Vue.component("async", {
props: {
url: { type: String, default: "", required: true },
params: { type: Object, default: () => ({}) }
},
data() {
return {
pending: true,
error: false,
data: null
};
},
watch: {
url() {
this.requestData();
},
params: {
handler() {
this.requestData();
},
deep: true
}
},
mounted() {
this.requestData();
},
methods: {
async requestData() {
this.pending = true;
try {
const { data } = await axios.get(this.url, { params: this.params });
this.data = data;
this.error = false;
} catch (e) {
this.data = null;
this.error = e;
}
this.pending = false;
}
},
render() {
return this.$scopedSlots.default({
pending: this.pending,
error: this.error,
data: this.data
});
}
});
注意:我在这里使用了一些 javascript 魔法:箭头函数、Async/Await和try...catch。
“无渲染”发生在render()
标签中。这些组件不使用 HTML 标签,而是仅将其在插槽中接收到的 HTML 代码段渲染为作用域插槽,并向其传递三个数据点:pending
、error
和data
。
这些watch
函数确保无论何时url
或params
发生改变,数据都会重新加载。
我们在模板中使用异步组件,如下所示:
<async url="https://dog.ceo/api/breed/husky/images">
<template v-slot:default="{ pending, error, data }">
<div v-if="pending">Loading ...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>{{ data }}</div>
</template>
</async>
为什么是无渲染组件而不是混合或指令?
在 Vue 中,组件并非代码复用的唯一方式,另一种方式是使用Mixin或自定义指令。这两种方法都能很好地解决这个问题。使用作用域插槽的无渲染组件正是 Vue 所期望的,它可以在需要时导入,就像你习惯于导入其他组件一样。因此,与无需单独导入的 mixin 或指令相比,这是一种非常明确的代码复用方式。最终,这取决于你的偏好。
一个应用示例
我在使用 API 时经常会遇到列表操作,这些 API 通常包含分页、筛选、排序和搜索等功能。因此,我决定编写一个“真实”示例,渲染一个简单的狗狗图片列表,并为不同品种设置一个非常简单的筛选选项(并调用一次错误的 API 来查看错误状态):
每当点击其中一个过滤器按钮时,传递给异步组件的 URL 都会更新为相应的品种。异步组件负责处理 HTTP 请求。父组件中不再需要 HTTP 请求逻辑,从而遵循了关注点分离原则,我们的思维得到了解放,世界也因此变得和谐统一 😄。
文章来源:https://dev.to/lhermann/vue-js-pattern-for-async-requests-using-renderless-components-3gd