前天有位佬友说他订阅了我博客的 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.tsxexport 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

request.ts

api.addRuntimePluginKey是指增加运行插件时的 key,应该是对应文档里指的运行时进行配置

request.ts

request.ts

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

小碎碎念:

  1. 查看项目 umi 版本时偶然发现 antd-pro 版本是 5.2.0 -v- 好浪漫的版本号

  2. 翻 umi 仓库时看到了仓库里友情提供的 demo,偶然发现有个 demo 的 readme.md 似乎是直接把另个 demo 的 readme.md 复制过来了,但是忘记改描述了,于是我给 umi 提了人生中第一个向大型项目提的 pr。我一边改 readme 一边想着,之前也看到过一个学长在博客里写自己向 Vue 提过一个 pr,当时看到这个的我超震惊,后来偶然跟学长聊起来这个,学长说他只是帮忙改文档的错别字哈哈哈哈

  3. 一边看源码一边感慨,到底要写多少代码才能架构出这么庞大的项目,完成这么多功能的集成配置,也不知道组里会怎么教新人上手维护这么大一个项目 qwq

  4. 项目今天又要写不完了 QAQ


后续补充:

其实今天在配置项目的 request 的时候一直报错:

request.ts

起初完全没明白为什么,然后去源码仓库全局查了一下这个报错的来源

request.ts

看得出来这个报错是出现在 plugin 的注册中,报错提示说这个 key 没有关联的 plugin

而我们项目里,我是将 request 和两个拦截器都写在了app.tsx中,并且三个变量全部被 export,导致出现报错。佬友 debug 的时候说app.tsx里不能直接写拦截器,得写到其他文件里再引用回来

所以我对这部分有了进一步的猜想:项目中app.tsx中被 export 的变量会被当做 plugin 的 key 名,antd pro 会遍历这些变量并找到对应的插件进行注册,项目中的变量request就是这样作为一个 key 被捕捉到了,如此就能获取到用户给变量request的配置,再将其关联到 umi 的 request 方法中,这样在逻辑上终于是说得通的了