一、Taro 为什么不直接用 fetch/axios,而是封装了一层跨端 API
同一套代码要运行在:
- 微信小程序
- H5
- React Native
- 支付宝小程序
- 抖音小程序
所以 Taro 提供统一 API:
Taro.request()
底层自动适配:
// H5
window.fetch / XMLHttpRequest
// 微信小程序
wx.request
// 支付宝小程序
my.request
二、请求/响应拦截器(同一拦截器里写)
Taro 用的是洋葱模型:一个拦截器函数里,既可以在发请求前改参数,也可以在 chain.proceed() 返回的 Promise 上处理响应。
TypeScript
Taro.addInterceptor(chain => {
const { requestParams } = chain;
const { url } = requestParams;
// 请求发出前
return chain.proceed({
...requestParams,
header: {
...requestParams.header,
Authorization: getToken(),
},
}).then(res => {
// 响应回来后
if (res.statusCode === 401) {
// 跳转登录等
}
if (res.data?.code !== 0) {
Taro.showToast({ title: res.data?.message ?? '请求失败', icon: 'none' });
return Promise.reject(res);
}
return res;
});
});
三、企业项目
通常封装:
1、网络请求基础地址
TypeScript
const serverUrl = process.env.NODE_ENV === 'development' ? '/proxy' : '';
2、错误管理器
TypeScript
const errorMessageManager = {
messages: new Map<string, number>(),
timeout: 3000,
showErrorOnce(key: string, msg: string) {
const now = Date.now();
const lastShown = this.messages.get(key);
if (!lastShown || now - lastShown > this.timeout) {
Taro.showToast({ title: msg, icon: 'none', duration: 2000 });
this.messages.set(key, now);
}
},
};
3、ts类型定义
TypeScript
type RequestConfig = Taro.request.Option & {
ignoreError?: boolean;
params?: Record<string, unknown>;
};
// 定义一个新类型 HttpExtraConfig,它等于 RequestConfig 去掉 url、method、data 这三个属性之后剩下的部分。
// 2. Omit<T, K>
// TypeScript 内置工具类型,含义是:
// Omit<某个类型, 要排除的键>
// 第一个参数 T:源类型
// 第二个参数 K:要从 T 里排除的属性名
type HttpExtraConfig = Omit<RequestConfig, 'url' | 'method' | 'data'>;
4、封装请求前的处理(类似于axios的请求拦截)
TypeScript
function patchRequestParams(params: Taro.request.Option): Taro.request.Option {
return {
...params,
url: `${serverUrl}${params.url}`,
header: {
...params.header,
Authorization: getToken(),
},
};
}
5、封装响应后的处理(响应拦截)
TypeScript
function handleResponse(
res: Taro.request.SuccessCallbackResult,
params: Taro.request.Option & { ignoreError?: boolean },
) {
const { statusCode } = res;
const ignoreError = (params as any).ignoreError;
if (statusCode === 401) {
errorMessageManager.showErrorOnce('auth-401', '登录过期,请重新登录!');
setTimeout(() => Taro.reLaunch({ url: '/pages/login/index' }), 500);
throw res;
}
if (!ignoreError && (statusCode === 400 || statusCode >= 500)) {
const msg = (res.data as { message?: string })?.message || '请求失败';
errorMessageManager.showErrorOnce(`error-${statusCode}`, msg);
throw res;
}
return res;
}
6、注册全局拦截器(需在 app 启动时调用一次)
TypeScript
export function setupRequestInterceptors() {
Taro.addInterceptor((chain) => {
const params = patchRequestParams(chain.requestParams);
return chain.proceed(params).then((res) => handleResponse(res, params));
});
}
7、封装 get 请求
TypeScript
export const get = (url: string, params = {}, config: HttpExtraConfig = {}) =>
Taro.request({
...config,
url,
method: 'GET',
data: params,
}).then((res) => (config.responseType ? res : res.data));
8、封装 post 请求
TypeScript
export const post = (
url: string,
data?: unknown,
config: HttpExtraConfig = {},
) =>
Taro.request({
...config,
url,
method: 'POST',
data,
}).then((res) => (config.responseType ? res : res.data));
9、封装 put 请求
TypeScript
export const put = (
url: string,
data?: unknown,
config: HttpExtraConfig = {},
) =>
Taro.request({
...config,
url,
method: 'PUT',
data,
}).then((res) => res.data);
10、封装 del 请求
TypeScript
export const del = (url: string, params = {}, config: HttpExtraConfig = {}) =>
Taro.request({
...config,
url,
method: 'DELETE',
data: params,
}).then((res) => res.data);