改善网页界面的响应性通常始于优化前端与后端的通信。在我们基于 Vue.js 的 Semaphore UI 中,我们引入了几种技术,以使数据加载对用户来说更快、更顺畅。本文分享了我们在版本 2.14 中实施的三项最具影响力的更改。
为什么要优化 API 使用?
API 通常是动态网页应用的支柱,但管理不当的请求可能会造成瓶颈。顺序调用的长等待时间或冗余的数据获取会降低用户体验。让我们直面这些问题。
1. 使用 Promise.all()
进行并行请求
问题:顺序请求
一个常见的陷阱是一个接一个地进行 API 调用。例如,先获取用户详细信息,然后是他们的订单,再然后是他们的偏好:
// 顺序方法(慢 😞)
async function fetchData() {
const user = await getUser();
const inventory = await getInventory(projectID);
const templates = await getTemplates(projectID);
return { user, inventory, templates };
}
在这里,每个请求都在等待前一个请求完成。如果每个调用需要 200 毫秒,总共需要 600 毫秒——这是一个明显的延迟。
解决方案:并行执行
Vue.js 利用 JavaScript 的异步能力,因此我们可以使用 Promise.all()
同时发出多个请求:
// 并行方法(快 ⚡)
async function fetchDataParallel() {
const [user, inventory, templates] = await Promise.all([
getUser(),
getInventory(projectID),
getTemplates(projectID),
]);
return { user, inventory, templates };
}
通过将独立请求分组,所有三个调用同时解决。如果每个请求需要 200 毫秒,总时间降至 200 毫秒!
与 Vue 集成
在 Vue 组件中,在生命周期钩子或方法中使用此模式:
export default {
props: {
projectID: Number,
},
watch: {
async projectID() {
await this.fetchDataParallel();
},
},
async created() {
await this.fetchDataParallel();
},
methods: {
async fetchDataParallel() {
try {
const [
this.user,
this.inventory,
this.templates
] = await Promise.all([
getUser(),
getInventory(this.projectID),
getTemplates(this.projectID),
]);
} catch (error) {
console.error("加载数据失败:", error);
}
};
}
提示: 将此与骨架加载器结合使用,以在数据加载时保持用户的参与感。
2. 重用缓存的 API 响应
问题:冗余获取
应用程序通常在多个组件中重新获取相同的数据(例如,多个视图请求的用户个人资料数据)。这浪费带宽并减慢界面速度。
解决方案:缓存和重用
存储 API 响应并在失效之前重用它们。Vue 的响应系统使得使用集中式存储(如 Pinia 或 Vuex)变得简单:
步骤 1:创建缓存存储
// stores/apiCache.js
import { defineStore } from 'pinia';
export const useApiCache = defineStore('apiCache', {
state: () => ({
project: null,
templates: {},
}),
actions: {
async fetchProject() {
if (!this.project) {
this.project = await getProject();
}
return this.project;
},
async fetchTemplates(id) {
if (!this.templates[id]) {
this.templates[id] = await fetchTemplates(id);
}
return this.templates[id];
},
},
});
步骤 2:在组件中使用存储
// Component.vue
import { useApiCache } from '@/stores/apiCache';
export default {
setup() {
const apiCache = useApiCache();
// 重用缓存的用户或仅获取一次
const user = apiCache.fetchUser();
return { user };
},
};
提示: 在 Semaphore UI 中,我们尚未使用 Pinia,但我们计划很快迁移到它。
缓存失效策略
- 基于时间的过期: 在设定的时间后删除缓存数据。
- 手动触发: 在变更后失效(例如,当用户更新其个人资料时)。
- Vue 的响应性: 当应用状态变化时重置数据(如注销时)。
3. 使用骨架加载器实现更平滑的过渡
问题:加载期间的无响应 UI
即使优化了 API 调用,用户在数据加载时仍会面临短暂的延迟。在此期间的空白屏幕或冻结界面可能会使您的应用感觉不够精致或缓慢,即使实际的获取时间很短。
解决方案:骨架加载器
骨架占位符在数据加载时模拟内容的布局,提供视觉反馈,使用户保持参与感。结合并行请求和缓存,它们创造了一个无缝的 速度感知。
如何在 Vue.js 中添加骨架加载器
步骤 1:选择骨架组件
在 Semaphore UI 中,我们使用 Vuetify 框架。它已经包含了骨架加载器组件。
<v-skeleton-loader
type="
table-heading,
image,
list-item-two-line,
list-item-two-line,
list-item-two-line"
></v-skeleton-loader>
步骤 2:与异步数据获取集成 使用加载状态在骨架和真实内容之间切换:
// Component.vue
export default {
data() {
return {
isLoading: true,
user: null,
templates: [],
};
},
async created() {
this.isLoading = true;
try {
[this.user, this.templates] = await Promise.all([
fetchUser(),
fetchTemplates(),
]);
} finally {
this.isLoading = false;
}
},
};
步骤 3:有条件地渲染骨架
<template>
<div class="user-profile">
<v-skeleton-loader
v-if="isLoading"
type="
table-heading,
image,
list-item-two-line,
list-item-two-line,
list-item-two-line"
></v-skeleton-loader>
<v-card v-else>
<h2>{{ user.name }}</h2>
<v-card v-for="tpl in templates" :key="tpl.id">
<v-card-title>{{ tpl.name }}</v-card-title>
<v-card-text>{{ tpl.description }}</v-card-text>
</v-card>
</v-card>
</div>
</template>
加载器设计的高级提示
- 匹配内容结构: 设计骨架以镜像您的实际布局(例如,图像占位符、文本行)。
- 优先考虑首屏内容: 首先显示可见内容的加载器,同时后台数据完成加载。
- 与缓存结合: 如果存在缓存数据,跳过骨架并立即显示数据。
为什么这很重要
- 用户体验: 74% 的用户注意到加载时间 (PWA Stats)。
- 感知性能: 加载器使等待时间感觉缩短 15-30% (NNGroup Research)。
- 品牌信任: 精致的过渡传达专业性。
将所有内容整合在一起
通过结合 并行请求、智能缓存 和 骨架加载器,我们能够实现显著的改进:
- 更快的初始加载: 并行请求显著缩短了数据加载时间。
- 更顺畅的导航: 重用响应减少了冗余的网络调用。
- 增强的用户参与感: 骨架加载器改善了视觉反馈和感知速度。
优化 API 使用不仅仅是关于原始性能——它还改善了用户体验。我们将继续优化我们的前端,使 Semaphore UI 在每次更新中变得更快、更友好。