Vue 工具库:Lodash、Axios封装(HTTP请求)
【摘要】 一、引言1.1 工具库在现代Vue开发中的重要性在现代Vue应用开发中,工具库的合理封装是提升开发效率、保证代码质量的关键。Lodash和Axios作为最常用的JavaScript工具库,其正确封装和使用直接影响应用的性能、可维护性和开发体验。1.2 技术选型价值分析class ToolLibraryAnalysis { /** 工具库市场分析 */ static getMark...
一、引言
1.1 工具库在现代Vue开发中的重要性
1.2 技术选型价值分析
class ToolLibraryAnalysis {
/** 工具库市场分析 */
static getMarketAnalysis() {
return {
'Lodash使用率': 'npm周下载量4000万+,前端项目使用率85%',
'Axios使用率': 'npm周下载量3500万+,HTTP客户端市场份额70%',
'开发效率提升': '工具库封装可提升30-50%开发效率',
'代码质量提升': '减少60%的边界情况处理代码',
'团队协作': '统一工具方法提升团队协作效率'
};
}
/** 技术方案对比 */
static getTechnologyComparison() {
return {
'Lodash vs 原生JS': {
'代码简洁性': '⭐⭐⭐⭐⭐ vs ⭐⭐',
'性能表现': '⭐⭐⭐⭐ vs ⭐⭐⭐⭐⭐',
'功能完整性': '⭐⭐⭐⭐⭐ vs ⭐⭐',
'学习成本': '⭐⭐⭐ vs ⭐⭐⭐⭐⭐',
'维护性': '⭐⭐⭐⭐⭐ vs ⭐⭐⭐'
},
'Axios vs Fetch': {
'浏览器兼容性': '⭐⭐⭐⭐⭐ vs ⭐⭐⭐',
'拦截器支持': '⭐⭐⭐⭐⭐ vs ⭐',
'请求取消': '⭐⭐⭐⭐⭐ vs ⭐⭐',
'进度监控': '⭐⭐⭐⭐ vs ⭐',
'包大小': '⭐⭐⭐ vs ⭐⭐⭐⭐⭐'
},
'封装 vs 直接使用': {
'统一管理': '⭐⭐⭐⭐⭐ vs ⭐⭐',
'团队规范': '⭐⭐⭐⭐⭐ vs ⭐',
'后期维护': '⭐⭐⭐⭐⭐ vs ⭐⭐',
'学习曲线': '⭐⭐⭐ vs ⭐⭐⭐⭐⭐',
'灵活性': '⭐⭐⭐ vs ⭐⭐⭐⭐⭐'
}
};
}
/** 封装策略指南 */
static getEncapsulationStrategy() {
return {
'需要封装的情况': [
'团队规模3人以上',
'项目复杂度中等以上',
'需要统一错误处理',
'需要监控和日志',
'长期维护项目'
],
'直接使用的情况': [
'小型个人项目',
'快速原型开发',
'特定功能需求',
'性能极致要求',
'技术验证阶段'
]
};
}
}
1.3 性能基准对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
二、技术背景
2.1 Lodash 核心架构
graph TB
A[Lodash架构] --> B[核心模块]
A --> C[功能分类]
A --> D[优化策略]
B --> B1[基础工具]
B --> B2[数组处理]
B --> B3[对象操作]
B --> B4[函数式编程]
B --> B5[实用工具]
C --> C1[集合操作]
C --> C2[字符串处理]
C --> C3[数学计算]
C --> C4[日期处理]
D --> D1[树摇优化]
D --> D2[懒加载]
D --> D3[方法缓存]
D --> D4[性能优化]
B1 --> E[Vue集成]
C1 --> E
D1 --> E
E --> F[高效开发]
2.2 Axios 拦截器架构
graph LR
A[请求发起] --> B[请求拦截器]
B --> C[适配器处理]
C --> D[响应拦截器]
D --> E[结果返回]
B --> B1[认证处理]
B --> B2[参数处理]
B --> B3[日志记录]
D --> D1[错误处理]
D --> D2[数据转换]
D --> D3[缓存处理]
B1 --> F[统一管理]
D1 --> F
三、环境准备与项目配置
3.1 项目依赖配置
// package.json
{
"name": "vue-tool-library",
"version": "1.0.0",
"dependencies": {
"vue": "^3.3.0",
"vue-router": "^4.0.0",
"pinia": "^2.0.0",
"axios": "^1.0.0",
"lodash": "^4.17.0",
"lodash-es": "^4.17.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.0.0",
"typescript": "^5.0.0",
"@types/lodash": "^4.14.0",
"vite-plugin-compression": "^2.0.0"
}
}
3.2 Vite 构建配置
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { compression } from 'vite-plugin-compression';
export default defineConfig({
plugins: [
vue(),
compression({
algorithm: 'gzip',
ext: '.gz'
})
],
build: {
rollupOptions: {
external: ['lodash', 'axios'],
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'tool-libs': ['lodash', 'axios'],
'utils': ['src/utils/**']
}
}
}
},
optimizeDeps: {
include: ['lodash', 'axios']
}
});
3.3 TypeScript 类型配置
// tsconfig.json
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": ["es2020", "dom", "dom.iterable"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"lodash/*": ["node_modules/@types/lodash/*"]
}
},
"include": [
"src/**/*",
"src/**/*.vue",
"types/**/*.d.ts"
],
"exclude": ["node_modules"]
}
四、Lodash 深度封装与实践
4.1 按需引入配置
// src/utils/lodash-loader.ts
import type { DebouncedFunc, ThrottleSettings } from 'lodash';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';
import get from 'lodash/get';
import set from 'lodash/set';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import uniqBy from 'lodash/uniqBy';
// 类型导出
export type { DebouncedFunc, ThrottleSettings };
// 工具方法封装
export class LodashUtils {
/**
* 防抖函数封装
*/
static createDebounce<T extends (...args: any[]) => any>(
func: T,
wait: number = 300,
options?: Parameters<typeof debounce>[2]
): DebouncedFunc<T> {
return debounce(func, wait, {
leading: false,
trailing: true,
...options
});
}
/**
* 节流函数封装
*/
static createThrottle<T extends (...args: any[]) => any>(
func: T,
wait: number = 300,
options?: ThrottleSettings
): DebouncedFunc<T> {
return throttle(func, wait, {
leading: true,
trailing: true,
...options
});
}
/**
* 深度克隆(支持循环引用)
*/
static deepClone<T>(value: T): T {
try {
return cloneDeep(value);
} catch (error) {
console.warn('深度克隆失败,使用JSON方式:', error);
return JSON.parse(JSON.stringify(value));
}
}
/**
* 深度比较
*/
static isDeepEqual(value: any, other: any): boolean {
return isEqual(value, other);
}
/**
* 安全获取对象属性
*/
static safeGet<T>(object: any, path: string | string[], defaultValue?: T): T {
return get(object, path, defaultValue) as T;
}
/**
* 安全设置对象属性
*/
static safeSet<T>(object: any, path: string | string[], value: any): T {
return set(object, path, value);
}
/**
* 排除对象属性
*/
static omitFields<T extends object, K extends keyof T>(object: T, paths: K[]): Omit<T, K> {
return omit(object, paths);
}
/**
* 选取对象属性
*/
static pickFields<T extends object, K extends keyof T>(object: T, paths: K[]): Pick<T, K> {
return pick(object, paths);
}
/**
* 数组分组
*/
static groupBy<T>(collection: T[], iteratee: string | ((item: T) => any)): Record<string, T[]> {
return groupBy(collection, iteratee);
}
/**
* 数组排序
*/
static orderBy<T>(
collection: T[],
iteratees: string | string[] | ((item: T) => any)[],
orders?: ('asc' | 'desc')[]
): T[] {
return orderBy(collection, iteratees, orders);
}
/**
* 数组去重
*/
static uniqBy<T>(collection: T[], iteratee: string | ((item: T) => any)): T[] {
return uniqBy(collection, iteratee);
}
}
// Vue Composition API 集成
export const useLodash = () => {
const { debounce, throttle } = LodashUtils;
return {
// 响应式防抖
useDebounceFn: <T extends (...args: any[]) => any>(
fn: T,
wait?: number,
options?: Parameters<typeof debounce>[2]
) => {
return debounce(fn, wait, options);
},
// 响应式节流
useThrottleFn: <T extends (...args: any[]) => any>(
fn: T,
wait?: number,
options?: ThrottleSettings
) => {
return throttle(fn, wait, options);
},
// 响应式深度监听
useDeepWatch: (source: any, callback: (newVal: any, oldVal: any) => void) => {
const isEqualRef = ref(false);
watch(source, (newVal, oldVal) => {
if (!LodashUtils.isDeepEqual(newVal, oldVal)) {
callback(newVal, oldVal);
}
}, { deep: true });
return isEqualRef;
}
};
};
// 默认导出
export default LodashUtils;
4.2 性能优化版本
// src/utils/lodash-optimized.ts
import { memoize } from 'lodash';
/**
* 高性能工具类 - 针对大数据量优化
*/
export class OptimizedLodashUtils {
private static cache = new Map<string, any>();
/**
* 带缓存的分组操作
*/
static cachedGroupBy<T>(
collection: T[],
iteratee: string | ((item: T) => any),
cacheKey?: string
): Record<string, T[]> {
const key = cacheKey || JSON.stringify({ collection, iteratee });
if (this.cache.has(key)) {
return this.cache.get(key);
}
const result = groupBy(collection, iteratee);
this.cache.set(key, result);
// 限制缓存大小
if (this.cache.size > 100) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
return result;
}
/**
* 记忆化函数 - 高性能版本
*/
static memoizedFunction<T extends (...args: any[]) => any>(
func: T,
resolver?: (...args: any[]) => any
): T {
return memoize(func, resolver);
}
/**
* 批量操作优化
*/
static batchProcess<T, R>(
items: T[],
processor: (item: T) => R,
batchSize: number = 100
): R[] {
const results: R[] = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = batch.map(processor);
results.push(...batchResults);
}
return results;
}
/**
* 懒加载数据分组
*/
static createLazyGroupedData<T>(
dataSource: () => T[] | Promise<T[]>,
groupKey: string | ((item: T) => any)
) {
let cachedData: T[] | null = null;
let groupedResult: Record<string, T[]> | null = null;
return {
async getGroupedData(): Promise<Record<string, T[]>> {
if (groupedResult) {
return groupedResult;
}
if (!cachedData) {
const data = await dataSource();
cachedData = Array.isArray(data) ? data : [];
}
groupedResult = groupBy(cachedData, groupKey);
return groupedResult;
},
invalidateCache(): void {
cachedData = null;
groupedResult = null;
}
};
}
}
4.3 Vue Composable 集成
// src/composables/useLodash.ts
import { ref, watch, computed, onUnmounted } from 'vue';
import { LodashUtils, type DebouncedFunc } from '@/utils/lodash-loader';
/**
* Lodash Vue Composition API 集成
*/
export function useLodashComposable() {
/**
* 响应式防抖函数
*/
function useDebounce<T extends (...args: any[]) => any>(
fn: T,
wait: number = 300,
options?: Parameters<typeof LodashUtils.createDebounce>[2]
) {
const debouncedFn = ref<DebouncedFunc<T>>();
const isPending = ref(false);
const execute = ((...args: Parameters<T>) => {
isPending.value = true;
return debouncedFn.value?.(...args);
}) as T;
// 创建防抖函数
debouncedFn.value = LodashUtils.createDebounce(async (...args: Parameters<T>) => {
try {
const result = await fn(...args);
return result;
} finally {
isPending.value = false;
}
}, wait, options);
// 组件卸载时取消
onUnmounted(() => {
debouncedFn.value?.cancel();
});
return {
execute,
cancel: () => debouncedFn.value?.cancel(),
flush: () => debouncedFn.value?.flush(),
isPending: computed(() => isPending.value)
};
}
/**
* 响应式深度监听
*/
function useDeepWatch<T>(
source: () => T,
callback: (newVal: T, oldVal: T) => void,
options: { immediate?: boolean; deep?: boolean } = {}
) {
const previousValue = ref<T>();
watch(source, (newVal, oldVal) => {
if (!LodashUtils.isDeepEqual(newVal, oldVal)) {
callback(newVal, oldVal);
}
previousValue.value = LodashUtils.deepClone(newVal);
}, { deep: true, immediate: options.immediate });
return { previousValue };
}
/**
* 响应式数组操作
*/
function useArrayUtils<T>() {
const array = ref<T[]>([]);
const utils = {
// 分组
groupBy: (iteratee: string | ((item: T) => any)) => {
return computed(() => LodashUtils.groupBy(array.value, iteratee));
},
// 排序
orderBy: (iteratees: string | string[] | ((item: T) => any)[], orders?: ('asc' | 'desc')[]) => {
return computed(() => LodashUtils.orderBy(array.value, iteratees, orders));
},
// 去重
uniqBy: (iteratee: string | ((item: T) => any)) => {
return computed(() => LodashUtils.uniqBy(array.value, iteratee));
},
// 过滤
filterBy: (predicate: (item: T) => boolean) => {
return computed(() => array.value.filter(predicate));
}
};
return {
array,
...utils
};
}
/**
* 响应式对象操作
*/
function useObjectUtils<T extends object>() {
const obj = ref<T>({} as T);
const utils = {
// 安全获取
get: (path: string | string[], defaultValue?: any) => {
return computed(() => LodashUtils.safeGet(obj.value, path, defaultValue));
},
// 安全设置
set: (path: string | string[], value: any) => {
return LodashUtils.safeSet(obj.value, path, value);
},
// 排除字段
omit: (paths: string[]) => {
return computed(() => LodashUtils.omitFields(obj.value, paths));
},
// 选取字段
pick: (paths: string[]) => {
return computed(() => LodashUtils.pickFields(obj.value, paths));
}
};
return {
obj,
...utils
};
}
return {
useDebounce,
useDeepWatch,
useArrayUtils,
useObjectUtils
};
}
// 默认导出
export const { useDebounce, useDeepWatch, useArrayUtils, useObjectUtils } = useLodashComposable();
五、Axios 深度封装与实践
5.1 基础Axios实例封装
// src/utils/axios-instance.ts
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
type InternalAxiosRequestConfig,
AxiosError
} from 'axios';
import { message } from 'ant-design-vue';
import { useUserStore } from '@/stores/user';
// 基础配置
const BASE_CONFIG: AxiosRequestConfig = {
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
};
// 响应数据格式
export interface ResponseData<T = any> {
code: number;
message: string;
data: T;
success: boolean;
timestamp: number;
}
// 扩展AxiosRequestConfig
declare module 'axios' {
interface AxiosRequestConfig {
// 是否显示错误消息
showError?: boolean;
// 是否显示成功消息
showSuccess?: boolean;
// 重试次数
retry?: number;
// 重试延迟
retryDelay?: number;
// 是否缓存
cache?: boolean;
// 缓存时间(毫秒)
cacheTime?: number;
}
}
/**
* 创建Axios实例
*/
export function createAxiosInstance(config: AxiosRequestConfig = {}): AxiosInstance {
const instance = axios.create({
...BASE_CONFIG,
...config
});
// 请求拦截器
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 添加时间戳防止缓存
if (config.method?.toUpperCase() === 'GET') {
config.params = {
...config.params,
_t: Date.now()
};
}
// 添加认证token
const userStore = useUserStore();
if (userStore.token && config.headers) {
config.headers.Authorization = `Bearer ${userStore.token}`;
}
// 显示加载状态
if (config.showLoading !== false) {
// 全局加载状态管理
}
console.log(`🚀 请求发起: ${config.method?.toUpperCase()} ${config.url}`, config);
return config;
},
(error: AxiosError) => {
console.error('❌ 请求拦截器错误:', error);
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response: AxiosResponse<ResponseData>) => {
console.log(`✅ 请求成功: ${response.config.url}`, response.data);
// 隐藏加载状态
if (response.config.showLoading !== false) {
// 隐藏全局加载状态
}
const { data, config } = response;
// 业务逻辑处理
if (data.code === 200 || data.success) {
// 显示成功消息
if (config.showSuccess && data.message) {
message.success(data.message);
}
return data;
} else {
// 业务错误处理
const error = new Error(data.message || '业务错误');
return Promise.reject(error);
}
},
(error: AxiosError) => {
console.error('❌ 响应拦截器错误:', error);
// 隐藏加载状态
if (error.config?.showLoading !== false) {
// 隐藏全局加载状态
}
// 错误处理
handleError(error);
return Promise.reject(error);
}
);
return instance;
}
/**
* 错误处理函数
*/
function handleError(error: AxiosError): void {
if (!error.config?.showError) return;
let errorMessage = '网络错误,请稍后重试';
if (error.response) {
// 服务器响应错误
const { status, data } = error.response;
switch (status) {
case 400:
errorMessage = '请求参数错误';
break;
case 401:
errorMessage = '未授权,请重新登录';
// 清除用户信息并跳转到登录页
const userStore = useUserStore();
userStore.logout();
break;
case 403:
errorMessage = '拒绝访问';
break;
case 404:
errorMessage = '请求资源不存在';
break;
case 500:
errorMessage = '服务器内部错误';
break;
case 502:
errorMessage = '网关错误';
break;
case 503:
errorMessage = '服务不可用';
break;
case 504:
errorMessage = '网关超时';
break;
default:
errorMessage = `网络错误: ${status}`;
}
// 使用服务器返回的错误消息
if (data && typeof data === 'object' && 'message' in data) {
errorMessage = (data as any).message;
}
} else if (error.request) {
// 网络错误
if (error.code === 'ECONNABORTED') {
errorMessage = '请求超时,请检查网络连接';
} else {
errorMessage = '网络错误,请检查网络连接';
}
} else {
// 其他错误
errorMessage = error.message || '未知错误';
}
message.error(errorMessage);
}
// 默认实例
export const http = createAxiosInstance();
// 文件上传实例
export const uploadHttp = createAxiosInstance({
baseURL: import.meta.env.VITE_UPLOAD_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'multipart/form-data'
}
});
// 外部API实例
export const externalHttp = createAxiosInstance({
baseURL: import.meta.env.VITE_EXTERNAL_API_BASE_URL,
timeout: 15000
});
5.2 高级功能封装
// src/utils/axios-advanced.ts
import { http, type ResponseData, type AxiosInstance } from './axios-instance';
/**
* 高级HTTP工具类
*/
export class AdvancedHttpClient {
private instance: AxiosInstance;
private cache = new Map<string, { data: any; expire: number }>();
constructor(instance: AxiosInstance) {
this.instance = instance;
}
/**
* 带缓存的GET请求
*/
async cachedGet<T = any>(
url: string,
config?: any,
cacheTime: number = 5 * 60 * 1000 // 默认5分钟
): Promise<T> {
const cacheKey = this.generateCacheKey('GET', url, config?.params);
// 检查缓存
const cached = this.cache.get(cacheKey);
if (cached && Date.now() < cached.expire) {
console.log('📦 使用缓存数据:', url);
return cached.data;
}
// 发起请求
const response = await this.instance.get<ResponseData<T>>(url, config);
// 缓存数据
this.cache.set(cacheKey, {
data: response.data,
expire: Date.now() + cacheTime
});
return response.data;
}
/**
* 带重试的请求
*/
async retryableRequest<T = any>(
requestFn: () => Promise<T>,
maxRetries: number = 3,
retryDelay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
lastError = error as Error;
console.warn(`请求失败,第${attempt}次重试:`, error);
if (attempt < maxRetries) {
await this.delay(retryDelay * attempt);
}
}
}
throw lastError!;
}
/**
* 并发请求控制
*/
async concurrentRequests<T>(
requests: (() => Promise<T>)[],
concurrency: number = 5
): Promise<T[]> {
const results: T[] = [];
const executing = new Set<Promise<any>>();
for (const request of requests) {
const promise = request().then(result => {
results.push(result);
executing.delete(promise);
});
executing.add(promise);
if (executing.size >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
/**
* 批量请求
*/
async batchRequests<T>(
urls: string[],
config?: any,
batchSize: number = 10
): Promise<T[]> {
const batches = [];
for (let i = 0; i < urls.length; i += batchSize) {
batches.push(urls.slice(i, i + batchSize));
}
const results: T[] = [];
for (const batch of batches) {
const batchRequests = batch.map(url =>
() => this.instance.get<ResponseData<T>>(url, config).then(r => r.data)
);
const batchResults = await this.concurrentRequests(batchRequests);
results.push(...batchResults);
}
return results;
}
/**
* 请求取消封装
*/
createCancelableRequest() {
const cancelToken = axios.CancelToken.source();
return {
request: <T = any>(config: any): Promise<T> => {
return this.instance.request({
...config,
cancelToken: cancelToken.token
});
},
cancel: (message?: string) => {
cancelToken.cancel(message);
}
};
}
/**
* 进度监控
*/
createUploadWithProgress(
url: string,
data: FormData,
onProgress?: (progress: number) => void
) {
return this.instance.post(url, data, {
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress(progress);
}
}
});
}
/**
* 生成缓存键
*/
private generateCacheKey(method: string, url: string, params?: any): string {
return `${method}:${url}:${JSON.stringify(params || {})}`;
}
/**
* 延迟函数
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 清理过期缓存
*/
clearExpiredCache(): void {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now >= value.expire) {
this.cache.delete(key);
}
}
}
/**
* 清空所有缓存
*/
clearAllCache(): void {
this.cache.clear();
}
}
// 创建高级客户端实例
export const advancedHttp = new AdvancedHttpClient(http);
5.3 Vue Composable 集成
// src/composables/useHttp.ts
import { ref, reactive, onUnmounted } from 'vue';
import { http, advancedHttp, type ResponseData } from '@/utils/axios-instance';
import type { AdvancedHttpClient } from '@/utils/axios-advanced';
/**
* HTTP请求Composable
*/
export function useHttp() {
const loading = ref(false);
const error = ref<Error | null>(null);
const data = ref<any>(null);
// 取消令牌
let cancelToken: any = null;
/**
* GET请求
*/
async function get<T = any>(
url: string,
config?: any
): Promise<ResponseData<T>> {
return executeRequest(() => http.get(url, config));
}
/**
* POST请求
*/
async function post<T = any>(
url: string,
data?: any,
config?: any
): Promise<ResponseData<T>> {
return executeRequest(() => http.post(url, data, config));
}
/**
* PUT请求
*/
async function put<T = any>(
url: string,
data?: any,
config?: any
): Promise<ResponseData<T>> {
return executeRequest(() => http.put(url, data, config));
}
/**
* DELETE请求
*/
async function del<T = any>(
url: string,
config?: any
): Promise<ResponseData<T>> {
return executeRequest(() => http.delete(url, config));
}
/**
* 执行请求
*/
async function executeRequest<T>(requestFn: () => Promise<T>): Promise<T> {
try {
loading.value = true;
error.value = null;
const result = await requestFn();
data.value = result;
return result;
} catch (err) {
error.value = err as Error;
throw err;
} finally {
loading.value = false;
}
}
/**
* 取消请求
*/
function cancel(message?: string): void {
if (cancelToken) {
cancelToken.cancel(message);
cancelToken = null;
}
}
/**
* 带自动取消的请求
*/
function useAutoCancelRequest() {
onUnmounted(() => {
cancel('组件卸载自动取消');
});
return {
get: async <T = any>(url: string, config?: any) => {
cancel('新请求取消前一个');
return get<T>(url, config);
},
cancel
};
}
/**
* 文件上传
*/
async function uploadFile(
url: string,
file: File,
onProgress?: (progress: number) => void
) {
const formData = new FormData();
formData.append('file', file);
return executeRequest(() =>
http.post(url, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress(progress);
}
}
})
);
}
return {
// 状态
loading,
error,
data,
// 方法
get,
post,
put,
delete: del,
cancel,
uploadFile,
useAutoCancelRequest,
// 高级功能
advanced: advancedHttp
};
}
/**
* 高级HTTP Composable
*/
export function useAdvancedHttp() {
const { advanced } = useHttp();
/**
* 带缓存的查询
*/
async function useCachedQuery<T = any>(
key: string,
queryFn: () => Promise<T>,
cacheTime: number = 5 * 60 * 1000
) {
const cacheKey = `cache_${key}`;
return advanced.cachedGet(cacheKey, {}, cacheTime);
}
/**
* 并发请求
*/
async function useConcurrentRequests<T>(
requests: (() => Promise<T>)[],
concurrency: number = 5
) {
return advanced.concurrentRequests(requests, concurrency);
}
/**
* 重试请求
*/
async function useRetryRequest<T = any>(
requestFn: () => Promise<T>,
maxRetries: number = 3
) {
return advanced.retryableRequest(requestFn, maxRetries);
}
return {
...advanced,
useCachedQuery,
useConcurrentRequests,
useRetryRequest
};
}
// 默认导出
export default useHttp;
六、实际应用示例
6.1 用户管理模块
<template>
<div class="user-management">
<!-- 搜索区域 -->
<div class="search-section">
<a-input-search
v-model:value="searchParams.keyword"
placeholder="搜索用户..."
enter-button="搜索"
@search="handleSearch"
@input="handleSearchInput"
/>
<a-button type="primary" @click="showModal">添加用户</a-button>
</div>
<!-- 用户列表 -->
<a-table
:columns="columns"
:data-source="userList"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
>
<template #action="{ record }">
<a-space>
<a-button size="small" @click="editUser(record)">编辑</a-button>
<a-button size="small" danger @click="deleteUser(record.id)">
删除
</a-button>
</a-space>
</template>
</a-table>
<!-- 添加/编辑用户模态框 -->
<a-modal
v-model:visible="modalVisible"
:title="isEditing ? '编辑用户' : '添加用户'"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
<a-form :model="formState" :rules="formRules" ref="formRef">
<a-form-item label="用户名" name="username">
<a-input v-model:value="formState.username" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="formState.email" />
</a-form-item>
<a-form-item label="角色" name="role">
<a-select v-model:value="formState.role">
<a-select-option value="user">用户</a-select-option>
<a-select-option value="admin">管理员</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue';
import { message } from 'ant-design-vue';
import { useDebounce, useDeepWatch } from '@/composables/useLodash';
import { useHttp, useAdvancedHttp } from '@/composables/useHttp';
import type { ColumnsType } from 'ant-design-vue/es/table';
interface User {
id: number;
username: string;
email: string;
role: string;
createTime: string;
}
interface SearchParams {
keyword: string;
role?: string;
page: number;
pageSize: number;
}
interface FormState {
username: string;
email: string;
role: string;
}
// 使用Composable
const { get, post, put, del, loading } = useHttp();
const { useCachedQuery } = useAdvancedHttp();
// 响应式数据
const userList = ref<User[]>([]);
const searchParams = reactive<SearchParams>({
keyword: '',
page: 1,
pageSize: 10
});
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true
});
// 模态框相关
const modalVisible = ref(false);
const isEditing = ref(false);
const editingUserId = ref<number | null>(null);
const formState = reactive<FormState>({
username: '',
email: '',
role: 'user'
});
// 表单验证规则
const formRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度3-20字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
]
};
// 表格列配置
const columns: ColumnsType = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80
},
{
title: '用户名',
dataIndex: 'username',
key: 'username'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '角色',
dataIndex: 'role',
key: 'role',
filters: [
{ text: '用户', value: 'user' },
{ text: '管理员', value: 'admin' }
]
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime'
},
{
title: '操作',
key: 'action',
slots: { customRender: 'action' }
}
];
// 防抖搜索
const { execute: debouncedSearch } = useDebounce(() => {
loadUserList();
}, 500);
// 监听搜索参数变化
watch(() => searchParams, () => {
debouncedSearch();
}, { deep: true });
// 生命周期
onMounted(() => {
loadUserList();
});
// 方法
async function loadUserList() {
try {
const response = await get<{ list: User[]; total: number }>('/api/users', {
params: searchParams
});
userList.value = response.data.list;
pagination.total = response.data.total;
} catch (error) {
message.error('加载用户列表失败');
}
}
function handleSearch() {
searchParams.page = 1;
loadUserList();
}
function handleSearchInput() {
debouncedSearch();
}
function handleTableChange(pag: any, filters: any) {
searchParams.page = pag.current;
searchParams.pageSize = pag.pageSize;
if (filters.role) {
searchParams.role = filters.role[0];
}
loadUserList();
}
function showModal() {
modalVisible.value = true;
isEditing.value = false;
Object.assign(formState, {
username: '',
email: '',
role: 'user'
});
}
function editUser(user: User) {
modalVisible.value = true;
isEditing.value = true;
editingUserId.value = user.id;
Object.assign(formState, {
username: user.username,
email: user.email,
role: user.role
});
}
async function deleteUser(userId: number) {
try {
await del(`/api/users/${userId}`);
message.success('删除用户成功');
loadUserList();
} catch (error) {
message.error('删除用户失败');
}
}
async function handleModalOk() {
try {
if (isEditing.value && editingUserId.value) {
await put(`/api/users/${editingUserId.value}`, formState);
message.success('更新用户成功');
} else {
await post('/api/users', formState);
message.success('添加用户成功');
}
modalVisible.value = false;
loadUserList();
} catch (error) {
message.error(isEditing.value ? '更新用户失败' : '添加用户失败');
}
}
function handleModalCancel() {
modalVisible.value = false;
}
</script>
<style scoped>
.user-management {
padding: 24px;
}
.search-section {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
</style>
6.2 数据仪表盘
<template>
<div class="data-dashboard">
<!-- 统计卡片 -->
<div class="stats-cards">
<a-row :gutter="16">
<a-col :span="6" v-for="stat in stats" :key="stat.title">
<a-card>
<div class="stat-card">
<div class="stat-icon" :style="{ color: stat.color }">
<component :is="stat.icon" />
</div>
<div class="stat-content">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-title">{{ stat.title }}</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 图表区域 -->
<div class="charts-section">
<a-row :gutter="16">
<a-col :span="12">
<a-card title="用户增长趋势">
<LineChart :data="userGrowthData" />
</a-card>
</a-col>
<a-col :span="12">
<a-card title="用户分布">
<PieChart :data="userDistributionData" />
</a-card>
</a-col>
</a-row>
</div>
<!-- 最近活动 -->
<div class="recent-activities">
<a-card title="最近活动">
<a-list :data-source="recentActivities">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta :description="item.time">
<template #title>
<span>{{ item.user }}</span>
<span class="activity-action">{{ item.action }}</span>
</template>
<template #avatar>
<a-avatar :src="item.avatar" />
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { UserOutlined, ShoppingCartOutlined, DollarOutlined, EyeOutlined } from '@ant-design/icons-vue';
import { useHttp, useAdvancedHttp } from '@/composables/useHttp';
import { useArrayUtils } from '@/composables/useLodash';
// 使用Composable
const { get, loading } = useHttp();
const { useCachedQuery } = useAdvancedHttp();
const { groupBy, orderBy } = useArrayUtils();
// 响应式数据
const rawData = ref<any>(null);
const stats = ref([
{ title: '总用户数', value: 0, icon: UserOutlined, color: '#1890ff' },
{ title: '总订单数', value: 0, icon: ShoppingCartOutlined, color: '#52c41a' },
{ title: '总销售额', value: 0, icon: DollarOutlined, color: '#faad14' },
{ title: '总访问量', value: 0, icon: EyeOutlined, color: '#f5222d' }
]);
// 计算属性
const userGrowthData = computed(() => {
if (!rawData.value) return [];
return rawData.value.userGrowth.map((item: any) => ({
date: item.date,
count: item.count
}));
});
const userDistributionData = computed(() => {
if (!rawData.value) return [];
return Object.entries(rawData.value.userDistribution).map(([name, value]) => ({
name,
value
}));
});
const recentActivities = computed(() => {
if (!rawData.value) return [];
return orderBy(rawData.value.activities, ['time'], ['desc']).slice(0, 5);
});
// 生命周期
onMounted(() => {
loadDashboardData();
});
// 方法
async function loadDashboardData() {
try {
// 使用缓存查询
const data = await useCachedQuery(
'dashboard_data',
() => get('/api/dashboard').then(r => r.data),
5 * 60 * 1000 // 缓存5分钟
);
rawData.value = data;
updateStats(data);
} catch (error) {
console.error('加载仪表盘数据失败:', error);
}
}
function updateStats(data: any) {
stats.value[0].value = data.totalUsers;
stats.value[1].value = data.totalOrders;
stats.value[2].value = data.totalSales;
stats.value[3].value = data.totalVisits;
}
// 自动刷新数据
setInterval(() => {
loadDashboardData();
}, 30000); // 每30秒刷新一次
</script>
<style scoped>
.data-dashboard {
padding: 24px;
}
.stats-cards {
margin-bottom: 24px;
}
.stat-card {
display: flex;
align-items: center;
}
.stat-icon {
font-size: 48px;
margin-right: 16px;
}
.stat-content .stat-value {
font-size: 24px;
font-weight: bold;
color: #000;
}
.stat-content .stat-title {
color: #999;
font-size: 14px;
}
.charts-section {
margin-bottom: 24px;
}
.activity-action {
margin-left: 8px;
color: #1890ff;
}
</style>
七、测试与质量保证
7.1 工具库单元测试
// tests/unit/lodash-utils.spec.ts
import { describe, it, expect, vi } from 'vitest';
import { LodashUtils } from '@/utils/lodash-loader';
describe('Lodash工具库测试', () => {
describe('防抖函数', () => {
it('应该正确防抖函数调用', async () => {
const mockFn = vi.fn();
const debouncedFn = LodashUtils.createDebounce(mockFn, 100);
// 快速连续调用
debouncedFn();
debouncedFn();
debouncedFn();
// 立即调用不应该执行
expect(mockFn).not.toHaveBeenCalled();
// 等待防抖时间
await new Promise(resolve => setTimeout(resolve, 150));
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该支持leading选项', async () => {
const mockFn = vi.fn();
const debouncedFn = LodashUtils.createDebounce(mockFn, 100, { leading: true });
debouncedFn();
expect(mockFn).toHaveBeenCalledTimes(1);
debouncedFn();
await new Promise(resolve => setTimeout(resolve, 150));
expect(mockFn).toHaveBeenCalledTimes(2);
});
});
describe('深度克隆', () => {
it('应该深度克隆对象', () => {
const original = { a: 1, b: { c: 2 } };
const cloned = LodashUtils.deepClone(original);
expect(cloned).toEqual(original);
expect(cloned).not.toBe(original);
expect(cloned.b).not.toBe(original.b);
});
it('应该处理循环引用', () => {
const obj: any = { a: 1 };
obj.self = obj;
const cloned = LodashUtils.deepClone(obj);
expect(cloned.self).toBe(cloned);
});
});
describe('安全获取', () => {
it('应该安全获取嵌套属性', () => {
const obj = { a: { b: { c: 1 } } };
expect(LodashUtils.safeGet(obj, 'a.b.c')).toBe(1);
expect(LodashUtils.safeGet(obj, 'a.b.d', 'default')).toBe('default');
expect(LodashUtils.safeGet(null, 'a.b.c')).toBeUndefined();
});
});
});
// tests/unit/http-utils.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { http, createAxiosInstance } from '@/utils/axios-instance';
import { AdvancedHttpClient } from '@/utils/axios-advanced';
describe('HTTP工具库测试', () => {
beforeEach(() => {
vi.resetAllMocks();
});
describe('Axios实例', () => {
it('应该创建带基础配置的实例', () => {
const instance = createAxiosInstance({
baseURL: 'https://api.example.com'
});
expect(instance.defaults.baseURL).toBe('https://api.example.com');
expect(instance.defaults.timeout).toBe(10000);
});
it('应该处理请求拦截器', async () => {
const mockAdapter = vi.fn().mockResolvedValue({ data: {} });
const instance = createAxiosInstance({
adapter: mockAdapter
});
await instance.get('/test');
expect(mockAdapter).toHaveBeenCalled();
});
});
describe('高级HTTP客户端', () => {
let advancedHttp: AdvancedHttpClient;
beforeEach(() => {
advancedHttp = new AdvancedHttpClient(http);
});
it('应该缓存GET请求', async () => {
const mockGet = vi.spyOn(http, 'get').mockResolvedValue({ data: 'cached' });
// 第一次请求
const result1 = await advancedHttp.cachedGet('/test');
expect(mockGet).toHaveBeenCalledTimes(1);
// 第二次请求应该使用缓存
const result2 = await advancedHttp.cachedGet('/test');
expect(mockGet).toHaveBeenCalledTimes(1);
expect(result2).toBe('cached');
});
it('应该支持请求重试', async () => {
let callCount = 0;
const mockRequest = vi.fn().mockImplementation(() => {
callCount++;
if (callCount < 3) {
return Promise.reject(new Error('Failed'));
}
return Promise.resolve('Success');
});
const result = await advancedHttp.retryableRequest(mockRequest, 3);
expect(callCount).toBe(3);
expect(result).toBe('Success');
});
});
});
7.2 集成测试
// tests/e2e/http-integration.spec.ts
import { test, expect } from '@playwright/test';
test.describe('HTTP工具集成测试', () => {
test('应该正确处理API请求', async ({ page }) => {
await page.goto('/');
// 模拟API响应
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 200,
data: {
list: [{ id: 1, name: 'Test User' }],
total: 1
},
success: true
})
});
});
// 触发API调用
await page.click('[data-testid="load-users"]');
// 验证数据渲染
await expect(page.locator('[data-testid="user-list"]')).toContainText('Test User');
});
test('应该处理网络错误', async ({ page }) => {
await page.goto('/');
// 模拟网络错误
await page.route('**/api/users', async route => {
await route.abort();
});
// 触发API调用
await page.click('[data-testid="load-users"]');
// 验证错误处理
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
});
test('应该支持请求取消', async ({ page }) => {
await page.goto('/');
// 触发快速连续请求
await page.click('[data-testid="search-input"]');
await page.fill('[data-testid="search-input"]', 'test');
// 验证防抖生效
await page.waitForTimeout(100);
const requestCount = await page.evaluate(() => {
return window.__REQUEST_COUNT__ || 0;
});
expect(requestCount).toBeLessThan(3); // 防抖应该限制请求次数
});
});
八、部署与优化
8.1 生产环境配置
// vite.config.prod.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
vue(),
visualizer({
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true
})
],
build: {
target: 'es2015',
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
rollupOptions: {
external: ['lodash', 'axios'],
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'lodash-core': ['lodash/debounce', 'lodash/throttle', 'lodash/cloneDeep'],
'lodash-array': ['lodash/groupBy', 'lodash/orderBy', 'lodash/uniqBy'],
'lodash-object': ['lodash/get', 'lodash/set', 'lodash/omit', 'lodash/pick'],
'axios-core': ['axios']
},
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
}
}
},
// 环境变量
define: {
'process.env.NODE_ENV': JSON.stringify('production')
}
});
8.2 性能优化策略
// src/utils/performance-optimizer.ts
export class PerformanceOptimizer {
/**
* 代码分割优化
*/
static async lazyLoadLodash() {
// 动态导入lodash模块
const lodash = await import(/* webpackChunkName: "lodash" */ 'lodash');
return lodash;
}
/**
* 树摇优化配置
*/
static getTreeShakeableImports() {
return {
lodash: {
// 只导入使用的模块
transform: 'lodash/${member}',
preventFullImport: true
},
axios: {
transform: 'axios',
preventFullImport: false
}
};
}
/**
* 缓存优化
*/
static setupCacheStrategies() {
// HTTP缓存
const cache = new Map();
return {
get: (key: string) => cache.get(key),
set: (key: string, value: any, ttl: number = 300000) => {
cache.set(key, value);
setTimeout(() => cache.delete(key), ttl);
},
clear: () => cache.clear()
};
}
/**
* 内存管理
*/
static setupMemoryManagement() {
let cleanupTimer: NodeJS.Timeout;
const cleanup = () => {
// 清理过期的缓存
if (typeof window !== 'undefined') {
window.gc?.(); // 强制垃圾回收(如果可用)
}
};
return {
start: () => {
cleanupTimer = setInterval(cleanup, 60000); // 每分钟清理一次
},
stop: () => {
clearInterval(cleanupTimer);
}
};
}
}
九、总结
9.1 技术成果总结
核心功能实现
- •
Lodash深度封装:按需引入、性能优化、Vue集成 - •
Axios高级封装:拦截器、缓存、重试、并发控制 - •
TypeScript全面支持:完整类型定义和类型安全 - •
Vue 3 Composition API集成:响应式工具函数 - •
性能优化体系:代码分割、懒加载、缓存策略
性能优化成果
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9.2 最佳实践总结
架构设计原则
const bestPractices = {
封装原则: [
'单一职责:每个工具类专注特定功能',
'开闭原则:对扩展开放,对修改关闭',
'依赖倒置:依赖抽象而非具体实现',
'接口隔离:细粒度接口设计'
],
性能优化: [
'按需引入:避免全量导入',
'懒加载:延迟加载非关键资源',
'缓存策略:合理使用内存和HTTP缓存',
'代码分割:合理拆分代码包'
],
开发体验: [
'类型安全:完整的TypeScript支持',
'文档完善:详细的API文档和示例',
'错误处理:统一的错误处理机制',
'调试支持:完善的日志和调试信息'
],
团队协作: [
'代码规范:统一的代码风格和规范',
'工具统一:团队使用相同的工具版本',
'文档驱动:完善的文档和示例',
'代码审查:严格的代码审查流程'
]
};
9.3 未来展望
技术演进趋势
const futureTrends = {
'2024-2025': {
'Bundleless开发': 'Vite、Snowpack等无打包工具普及',
'ES模块原生支持': '浏览器原生ES模块支持完善',
'WebAssembly集成': '高性能计算任务迁移到WASM',
'AI辅助开发': 'AI代码生成和优化建议'
},
'2026-2027': {
'量子计算准备': '量子计算相关工具链发展',
'边缘计算': '工具库对边缘计算场景优化',
'元宇宙开发': '3D和VR/AR开发工具集成',
'低代码集成': '工具库与低代码平台深度集成'
}
};
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)