弱网环境体验优化
8.2 请求优先级核心代码模板
一、核心代码模板
1. 请求优先级类型定义(TypeScript)
文件路径:src/types/request.ts
/**
* 请求优先级枚举
* 用于约束所有请求的优先级层级
*/
export enum RequestPriority {
HIGH = 'high', // 核心请求:首屏关键数据、用户主动操作
MEDIUM = 'medium', // 中等优先级:非核心但用户可见内容
LOW = 'low' // 低优先级:用户无感知、可延迟/降级内容
}
/**
* 带优先级的请求配置接口
* 扩展Axios请求配置,强制要求优先级参数
*/
import type { AxiosRequestConfig } from 'axios';
export interface PriorityAxiosRequestConfig extends AxiosRequestConfig {
priority: RequestPriority; // 必传,请求优先级
abortable?: boolean; // 可选,弱网下是否可中止(默认LOW可中止)
timeout?: number; // 可选,超时时间(默认HIGH=5s, MEDIUM=8s, LOW=10s)
}
2. 请求优先级队列实现
文件路径:src/utils/requestPriorityQueue.ts
import type { PriorityAxiosRequestConfig, RequestPriority } from '@/types/request';
import request from '@/utils/request'; // 后续配置的Axios实例
import { ref } from 'vue';
// 优先级权重映射(用于队列排序)
const PRIORITY_WEIGHT = {
[RequestPriority.HIGH]: 3,
[RequestPriority.MEDIUM]: 2,
[RequestPriority.LOW]: 1
};
/**
* 请求优先级队列类
* 功能:按优先级调度请求、控制并发数、弱网下智能中止低优先级请求
*/
export class RequestPriorityQueue {
private queue: [number, PriorityAxiosRequestConfig, AbortController][] = []; // 队列存储
private currentConcurrency = 0; // 当前并发数
private maxConcurrency = 3; // 弱网下默认最大并发数
public isWeakNetwork = ref(false); // 网络状态标记
constructor() {
this.listenNetworkStatus(); // 初始化时监听网络状态
}
/** 监听网络状态变化(判断是否为弱网) */
private listenNetworkStatus() {
if (!navigator.connection) return;
const updateStatus = () => {
const isWeak = ['2g', 'slow-2g'].includes(navigator.connection.effectiveType);
this.isWeakNetwork.value = isWeak;
this.maxConcurrency = isWeak ? 3 : 5; // 弱网降低并发数
if (isWeak) this.abortLowPriorityRequests(); // 弱网时中止低优先级请求
};
updateStatus(); // 初始检查
navigator.connection.addEventListener('change', updateStatus); // 状态变化监听
}
/** 添加请求到队列并执行 */
add<T = any>(config: PriorityAxiosRequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
// 补全默认配置
const completeConfig = this.completeDefaultConfig(config);
// 创建中止控制器
const controller = new AbortController();
completeConfig.signal = controller.signal;
// 计算权重并加入队列
const weight = PRIORITY_WEIGHT[completeConfig.priority];
this.queue.push([weight, completeConfig, controller]);
// 按权重排序(高优先级在前)
this.queue.sort((a, b) => b[0] - a[0] || 0);
// 执行队列
this.runQueue(resolve, reject);
});
}
/** 补全请求默认配置 */
private completeDefaultConfig(config: PriorityAxiosRequestConfig): PriorityAxiosRequestConfig {
return {
timeout: config.timeout || {
[RequestPriority.HIGH]: 5000,
[RequestPriority.MEDIUM]: 8000,
[RequestPriority.LOW]: 10000
}[config.priority],
abortable: config.abortable ?? config.priority === RequestPriority.LOW,
...config
};
}
/** 执行队列(控制并发) */
private async runQueue<T = any>(resolve: (data: T) => void, reject: (err: any) => void) {
if (this.currentConcurrency >= this.maxConcurrency || this.queue.length === 0) return;
// 取出队首请求(优先级最高)
const [_, config, controller] = this.queue.shift()!;
this.currentConcurrency++;
try {
// 发起请求
const response = await request<T>(config);
resolve(response.data);
} catch (error: any) {
// 核心请求失败重试1次(非中止错误)
if (config.priority === RequestPriority.HIGH && !error.name.includes('AbortError')) {
const retryRes = await request<T>(config);
resolve(retryRes.data);
} else {
reject(error);
}
} finally {
this.currentConcurrency--;
this.runQueue(resolve, reject); // 继续执行队列
}
}
/** 弱网时中止未执行的低优先级请求 */
private abortLowPriorityRequests() {
this.queue = this.queue.filter(([_, config, controller]) => {
if (config.priority === RequestPriority.LOW && config.abortable) {
controller.abort();
return false;
}
return true;
});
}
/** 清空队列(页面卸载时调用) */
clear() {
this.queue.forEach(([_, __, controller]) => controller.abort());
this.queue = [];
this.currentConcurrency = 0;
}
}
// 全局单例队列实例
export const requestQueue = new RequestPriorityQueue();
3. Axios配置(注入优先级标记)
文件路径:src/utils/request.ts
import axios from 'axios';
import type { PriorityAxiosRequestConfig, RequestPriority } from '@/types/request';
// 创建Axios实例
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
headers: { 'Content-Type': 'application/json' }
});
// 请求拦截器:注入优先级标记
instance.interceptors.request.use(
(config: PriorityAxiosRequestConfig) => {
// 校验必传参数
if (!config.priority) {
throw new Error('请求必须指定priority(RequestPriority)');
}
// 添加优先级Header(前后端协同)
config.headers['X-Request-Priority'] = config.priority;
// 若使用Fetch适配器,传递priority属性(需安装axios-fetch-adapter)
if (config.adapter === 'fetch') {
(config as any).priority = config.priority;
}
// 添加Token(根据项目实际情况调整)
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:处理中止错误
instance.interceptors.response.use(
(response) => response,
(error) => {
// 过滤弱网下主动中止的低优先级请求错误
if (error.name === 'AbortError') {
console.log(`请求已中止(弱网优化): ${error.config?.url}`);
return Promise.reject(new Error('请求已中止(弱网环境)'));
}
// 其他错误统一处理
console.error('请求错误:', error);
return Promise.reject(error);
}
);
// 导出带优先级约束的请求方法
export default {
get<T = any>(url: string, config: PriorityAxiosRequestConfig) {
return instance.get<T>(url, config);
},
post<T = any>(url: string, data?: any, config: PriorityAxiosRequestConfig) {
return instance.post<T>(url, data, config);
},
put<T = any>(url: string, data?: any, config: PriorityAxiosRequestConfig) {
return instance.put<T>(url, data, config);
},
delete<T = any>(url: string, config: PriorityAxiosRequestConfig) {
return instance.delete<T>(url, config);
}
};
4. Vue3组件使用示例
文件路径:src/views/HomePage.vue(首页示例)
<template>
<div class="home-page">
<!-- 核心内容区 -->
<div class="core-content">
<Skeleton v-if="!userInfo" />
<UserInfoCard :info="userInfo" v-else />
<Skeleton v-if="!goodsList" />
<GoodsDisplay :list="goodsList" v-else />
</div>
<!-- 非核心内容区(延迟加载) -->
<div class="non-core-content" v-if="showNonCore">
<h3>推荐内容</h3>
<RecommendList :data="recommendList" />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, nextTick } from 'vue';
import { requestQueue } from '@/utils/requestPriorityQueue';
import { RequestPriority } from '@/types/request';
import Skeleton from '@/components/Skeleton.vue';
import UserInfoCard from '@/components/UserInfoCard.vue';
import GoodsDisplay from '@/components/GoodsDisplay.vue';
import RecommendList from '@/components/RecommendList.vue';
// 状态定义
const userInfo = ref<any>(null);
const goodsList = ref<any[]>([]);
const recommendList = ref<any[]>([]);
const showNonCore = ref(false);
// 页面卸载时清空队列(避免内存泄漏)
onUnmounted(() => {
requestQueue.clear();
});
onMounted(async () => {
try {
// 1. 高优先级:核心请求(并行发起)
const [userRes, goodsRes] = await Promise.all([
requestQueue.add({
url: '/api/v1/user/current',
method: 'get',
priority: RequestPriority.HIGH
}),
requestQueue.add({
url: '/api/v1/goods/list',
method: 'get',
priority: RequestPriority.HIGH
})
]);
userInfo.value = userRes;
goodsList.value = goodsRes;
// 2. 中等优先级:公告(核心内容加载后发起)
const announcement = await requestQueue.add({
url: '/api/v1/system/announcement',
method: 'get',
priority: RequestPriority.MEDIUM
});
// 处理公告逻辑...
// 3. 低优先级:推荐列表(核心内容渲染后延迟发起)
await nextTick(); // 等待核心内容DOM渲染完成
showNonCore.value = true;
recommendList.value = await requestQueue.add({
url: '/api/v1/goods/recommend',
method: 'get',
priority: RequestPriority.LOW,
abortable: true // 弱网下可中止
});
// 4. 低优先级:行为统计(用户无感知)
await requestQueue.add({
url: '/api/v1/stat/behavior',
method: 'post',
data: { page: 'home', action: 'loaded' },
priority: RequestPriority.LOW,
abortable: true
});
} catch (error) {
console.error('首页加载失败:', error);
}
});
</script>
<style scoped>
.core-content {
margin-bottom: 20px;
}
.non-core-content {
opacity: 0.9; /* 视觉上区分非核心内容 */
}
</style>
二、小剧场:团队如何复用代码模板
场景:小美按照模板修改完首页代码后,在团队周会上分享复用经验。
人物:小美(前端开发)、小稳(技术负责人)、小燕(质量工程师)、大熊(后端开发)
小美:“上周按请求优先级模板改了首页,现在弱网下用户信息和商品列表加载速度快了3秒!我分享下怎么复用模板吧:首先复制request.ts类型定义,然后是队列和Axios配置,最后在组件里用requestQueue.add发起请求,指定优先级就行。”
小稳:“嗯,模板里的队列单例很重要,全局只需要一个实例,不然并发控制会失效。你们注意到abortable参数了吗?非核心请求一定要设为true,弱网时队列会自动中止它们,释放资源给核心请求。”
小燕:“我测试的时候发现,弱网下推荐列表确实会被中止,控制台会打印‘请求已中止(弱网优化)’,这时候用户只能看到核心内容,体验反而更流畅了。不过有个问题:核心请求失败后重试,怎么验证重试逻辑生效?”
小美:“可以在DevTools的Network面板把核心接口设为‘Block’,然后看日志——模板里的队列会自动重试1次,失败才会抛错。我还加了个Skeleton组件,请求未完成时显示骨架屏,用户感知会更好。”
大熊:“后端已经支持X-Request-Priority头了,收到高优先级请求会放到优先队列处理。你们用模板的时候记得别改这个Header的key,不然后端识别不了。”
小稳:“另外,超核心的请求(比如登录状态)建议在index.html里加<link rel="preload">预加载,模板里的组件示例没写这个,需要的话可以参考之前的例子补充。还有页面卸载时的clear()方法一定要调用,避免切换路由后请求还在跑。”
小美:“对了,TypeScript的类型约束帮我发现了好几个没加priority的请求,编译时就报错了,比线上出问题再排查高效多了!”
小燕:“那我今天就用这个模板测一下商品详情页,重点看核心请求是不是真的优先响应,弱网并发数是不是控制在3个以内。”
小稳:“好,模板复用的时候有问题随时提,确保所有页面都按这个标准处理请求优先级,弱网体验就能统一优化了。”
复用说明
- 模板依赖
axios,需确保项目已安装(npm install axios); - 若需使用Fetch的
priority属性,需安装Fetch适配器:npm install axios-fetch-adapter,并在请求配置中指定adapter: 'fetch'; - 网络状态监听依赖
navigator.connection,低版本浏览器可能不支持,可根据项目兼容性需求补充polyfill; - 优先级分类(哪些请求属于HIGH/LOW)需结合业务场景调整,模板仅提供基础框架。

1192

被折叠的 条评论
为什么被折叠?



