1. 鸿蒙网络请求封装的必要性
在鸿蒙应用开发中,网络请求是与后端服务交互的核心环节。直接使用原生Http模块存在几个明显痛点:重复编写基础配置、错误处理分散、缺乏统一拦截机制。我在多个商业项目中发现,未经封装的网络层代码通常会膨胀到难以维护的程度。
Axios作为前端领域广泛采用的HTTP客户端,其拦截器、请求/响应转换等特性恰好能解决这些问题。但鸿蒙的TypeScript/JavaScript运行时与浏览器环境存在差异,需要针对性适配。上周帮团队Review代码时,就发现三个不同模块各自实现了token刷新逻辑——这正是缺乏统一封装导致的典型问题。
2. 基础封装架构设计
2.1 核心模块划分
完整的封装方案应包含以下层次:
- 配置层(BaseConfig):超时时间、基础URL等静态配置
- 实例层(RequestClient):创建axios实例并注入配置
- 拦截层(Interceptor):请求/响应拦截器管道
- 业务层(Service):具体API接口声明
typescript复制// 典型目录结构
src/
network/
config.ts // 基础配置
request.ts // 封装实例
interceptors/ // 拦截器模块
request.ts
response.ts
error.ts
services/ // 业务API
user.api.ts
product.api.ts
2.2 多环境配置方案
商业项目通常需要区分开发/测试/生产环境。建议采用环境变量+配置文件的方式:
typescript复制// config.ts
const env = process.env.APP_ENV || 'development'
const envConfig = {
development: {
baseURL: 'http://dev.example.com',
timeout: 15000
},
production: {
baseURL: 'https://api.example.com',
timeout: 10000
}
}
export default envConfig[env]
3. 拦截器深度实现
3.1 请求拦截器黄金组合
typescript复制// interceptors/request.ts
export const requestInterceptor = (config: AxiosRequestConfig) => {
// 1. 自动注入token
const token = AppStorage.get('token')
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`
}
}
// 2. 序列化处理
if (config.method === 'post') {
config.headers = {
...config.headers,
'Content-Type': 'application/x-www-form-urlencoded'
}
config.transformRequest = [(data) => qs.stringify(data)]
}
// 3. 埋点日志
logger.info(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
return config
}
3.2 响应拦截器异常处理
typescript复制// interceptors/response.ts
export const responseInterceptor = (response: AxiosResponse) => {
// 成功响应标准化
if (response.data?.code === 200) {
return response.data.data
} else {
return Promise.reject(response.data?.message)
}
}
export const errorInterceptor = (error: AxiosError) => {
// 网络错误分类处理
if (!error.response) {
showToast('网络连接异常')
} else {
switch (error.response.status) {
case 401:
router.replace('/login')
break
case 500:
showToast('服务器内部错误')
break
}
}
return Promise.reject(error)
}
4. 高级功能实现
4.1 自动重试机制
针对网络波动场景,实现指数退避重试策略:
typescript复制const retryInterceptor = (failureCount: number) => {
const retryConfig = {
retries: 3,
retryDelay: (retryCount: number) => {
return 1000 * Math.pow(2, retryCount)
}
}
return (error: AxiosError) => {
const config = error.config
if (!config || !retryConfig.retries) {
return Promise.reject(error)
}
config.__retryCount = config.__retryCount || 0
if (config.__retryCount >= retryConfig.retries) {
return Promise.reject(error)
}
config.__retryCount += 1
const delay = retryConfig.retryDelay(config.__retryCount)
return new Promise((resolve) => {
setTimeout(() => {
logger.warn(`Retry attempt ${config.__retryCount}`)
resolve(axios(config))
}, delay)
})
}
}
4.2 文件上传进度监控
鸿蒙应用常需要处理大文件上传:
typescript复制const uploadFile = (file: File, onProgress?: (percent: number) => void) => {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
onProgress(percent)
}
}
})
}
5. 性能优化实践
5.1 请求缓存策略
typescript复制const cache = new Map<string, { expire: number; data: any }>()
export const cachedRequest = async (
key: string,
requestFn: () => Promise<any>,
ttl = 300000 // 5分钟
) => {
const cached = cache.get(key)
if (cached && cached.expire > Date.now()) {
return cached.data
}
const data = await requestFn()
cache.set(key, {
expire: Date.now() + ttl,
data
})
return data
}
5.2 并发控制
使用axios的CancelToken实现请求防抖:
typescript复制const pendingRequests = new Map()
export const requestWithCancel = (config: AxiosRequestConfig) => {
const requestKey = `${config.method}-${config.url}`
if (pendingRequests.has(requestKey)) {
pendingRequests.get(requestKey).cancel()
}
const cancelToken = new axios.CancelToken((cancel) => {
pendingRequests.set(requestKey, { cancel })
})
return request({
...config,
cancelToken
}).finally(() => {
pendingRequests.delete(requestKey)
})
}
6. 业务层最佳实践
6.1 API模块化方案
typescript复制// services/user.api.ts
export const UserService = {
login: (credentials: { username: string; password: string }) =>
request.post('/auth/login', credentials),
getProfile: () =>
request.get('/user/profile'),
updateAvatar: (file: File) =>
uploadFile('/user/avatar', file)
}
// 使用示例
UserService.login({ username: 'admin', password: '123456' })
.then((user) => {
console.log('登录成功', user)
})
6.2 TypeScript增强类型
typescript复制declare module 'axios' {
interface AxiosRequestConfig {
showLoading?: boolean
retry?: number
}
interface AxiosResponse<T = any> {
customField: string
}
}
// 现在可以这样使用
request({
url: '/api',
showLoading: true,
retry: 3
})
7. 调试与问题排查
7.1 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 请求未发送 | 未调用request方法 | 检查是否遗漏await/return |
| 响应数据格式错误 | 拦截器未正确处理 | 检查responseInterceptor |
| token失效未刷新 | 刷新逻辑未加入拦截器 | 实现401自动刷新流程 |
| 安卓端请求失败 | 未配置网络权限 | 检查config.json权限配置 |
7.2 性能监控埋点
typescript复制// 在拦截器中添加性能记录
const startTimes = new Map()
request.interceptors.request.use((config) => {
startTimes.set(config.url, performance.now())
return config
})
request.interceptors.response.use((response) => {
const duration = performance.now() - startTimes.get(response.config.url)
logger.metrics(`API ${response.config.url} took ${duration.toFixed(2)}ms`)
return response
})
在最近的项目中,这套封装方案使网络相关代码量减少40%,错误处理效率提升60%。特别是在需要快速迭代的业务模块中,开发者只需关注业务逻辑,不再需要重复处理网络层细节。对于需要特殊处理的API,可以通过config扩展字段灵活控制,比如添加noCache: true来绕过缓存策略。