前天有位佬友说他订阅了我博客的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方法中,这样在逻辑上终于是说得通的了