前天有位佬友说他订阅了我博客的 RSS,哭死 QAQ 不能在博客理发店了但是更博的动力++
这几天在写一个用 Antd Pro 起的项目,这位佬友热心地给了我一个关于书写 request 的代码模板,并且提了一句这些写在app.tsx
中。当时我并未引以为意,习惯性把请求相关的代码写到了src/utils/request
下,写完之后陷入了沉思:然后呢?这个 request 是要作为实例导出吗?可是它在文件里并没有引用任何请求实例,我只是给它配置了一些参数而已。
随后我跟这位佬友确认了一下,佬友回答说确实写在app.tsx
里配置就能生效,使用就只需要直接引入 umi 内置的 request 就可以。不单单是 request,app.tsx
里还写了 layout 相关的配置,依然是写在app.tsx
就生效。
然而这让我产生了一种不真实感,这是怎么配置上的呢?于是我打开官方文档开始研究。
官方文档提到因为构建时无法使用dom,所以有些配置可能需要运行时来配置,一般而言我们都是在src/app.tsx中管理运行时配置
,所以并不需要在src/utils/request.ts
新建一个请求实例进行配置再暴露出去,只用在src/app.tsx
中export const request
并对request
进行配置。
export const request: RequestConfig = {
errorHandler: (error: ResponseError) => {
const { messages } = getIntl(getLocale());
const { response } = error;
notification.error({
description: "您的网络发生异常,无法连接服务器",
message: "网络异常",
});
throw error;
},
};
但这里也只是介绍了用法,还是没说具体怎么配置的,于是我又去翻了 umi 的仓库源码。最后定位到有关 request 插件的代码是在 umi 仓库的packages/plugins/src/request.ts
里
api.addRuntimePluginKey
是指增加运行插件时的 key,应该是对应文档里指的运行时进行配置
requestTpl
变量写入了一套利用 axios 配置 request 实例的模板字符串,同时动态获取模板字符串中代码所依赖的文件的路径,这样二者合在一起写入新的request.ts
中,动态生成了一个 request 实例
具体而言,requestTpl
中定义了一个 request 实例requestInstance
和 request 实例的配置config
,刚好这个config
的接口类型和我们在项目的app.tsx
中定义的 request 配置的接口类型同为RequestConfig
。可惜 antd-pro 没有开源,这里只能进行一个猜测:antd 会将项目的app.tsx
中 export 的 request 作为 config 引入,如果 config 存在,则使用用户自定义的 config,否则使用默认的由 getPluginManager().applyPlugins 生成的 config,再利用 config 生成一个 axios 实例返回给 request 实例接收,然后检测 config 有没有挂载拦截器相关的属性,有则遍历添加到 request 实例中,最后再将 request 实例暴露出去,这样就能让用户无感知地直接在项目里import {useRequest} from 'umi'
// request.ts
let requestInstance: AxiosInstance;
let config: RequestConfig;
const getConfig = (): RequestConfig => {
if (config) return config;
config = getPluginManager().applyPlugins({
key: "request",
type: ApplyPluginsType.modify,
initialValue: {},
});
return config;
};
const getRequestInstance = (): AxiosInstance => {
if (requestInstance) return requestInstance;
const config = getConfig();
requestInstance = axios.create(config);
config?.requestInterceptors?.forEach((interceptor) => {
if (interceptor instanceof Array) {
requestInstance.interceptors.request.use((config) => {
const { url } = config;
if (interceptor[0].length === 2) {
const { url: newUrl, options } = interceptor[0](url, config);
return { ...options, url: newUrl };
}
return interceptor[0](config);
}, interceptor[1]);
} else {
requestInstance.interceptors.request.use((config) => {
const { url } = config;
if (interceptor.length === 2) {
const { url: newUrl, options } = interceptor(url, config);
return { ...options, url: newUrl };
}
return interceptor(config);
});
}
});
config?.responseInterceptors?.forEach((interceptor) => {
interceptor instanceof Array
? requestInstance.interceptors.response.use(
interceptor[0],
interceptor[1]
)
: requestInstance.interceptors.response.use(interceptor);
});
// 当响应的数据 success 是 false 的时候,抛出 error 以供 errorHandler 处理。
requestInstance.interceptors.response.use((response) => {
const { data } = response;
if (data?.success === false && config?.errorConfig?.errorThrower) {
config.errorConfig.errorThrower(data);
}
return response;
});
return requestInstance;
};
const request: IRequest = (url: string, opts: any = { method: "GET" }) => {
const requestInstance = getRequestInstance();
const config = getConfig();
// ...
};
一晚上看不懂一整个庞大的工程,先扔个猜想在这里,以后有能力了再回来勘勘误。还是赶紧把项目写完吧.jpg
小碎碎念:
查看项目 umi 版本时偶然发现 antd-pro 版本是 5.2.0 -v- 好浪漫的版本号
翻 umi 仓库时看到了仓库里友情提供的 demo,偶然发现有个 demo 的 readme.md 似乎是直接把另个 demo 的 readme.md 复制过来了,但是忘记改描述了,于是我给 umi 提了人生中第一个向大型项目提的 pr。我一边改 readme 一边想着,之前也看到过一个学长在博客里写自己向 Vue 提过一个 pr,当时看到这个的我超震惊,后来偶然跟学长聊起来这个,学长说他只是帮忙改文档的错别字哈哈哈哈
一边看源码一边感慨,到底要写多少代码才能架构出这么庞大的项目,完成这么多功能的集成配置,也不知道组里会怎么教新人上手维护这么大一个项目 qwq
项目今天又要写不完了 QAQ
后续补充:
其实今天在配置项目的 request 的时候一直报错:
起初完全没明白为什么,然后去源码仓库全局查了一下这个报错的来源
看得出来这个报错是出现在 plugin 的注册中,报错提示说这个 key 没有关联的 plugin
而我们项目里,我是将 request 和两个拦截器都写在了app.tsx
中,并且三个变量全部被 export,导致出现报错。佬友 debug 的时候说app.tsx
里不能直接写拦截器,得写到其他文件里再引用回来
所以我对这部分有了进一步的猜想:项目中app.tsx
中被 export 的变量会被当做 plugin 的 key 名,antd pro 会遍历这些变量并找到对应的插件进行注册,项目中的变量request
就是这样作为一个 key 被捕捉到了,如此就能获取到用户给变量request
的配置,再将其关联到 umi 的 request 方法中,这样在逻辑上终于是说得通的了