Axios: 使用响应拦截器时如何将 Axios 与 TypeScript 一起使用(AxiosResponse 问题)

创建于 2018-04-30  ·  50评论  ·  资料来源: axios/axios

概括

在我正在迁移到 TypeScript (TS) 的项目中,我有一个响应拦截器r => r.data 。 我如何通知 TS 我不期望AxiosResponse类型? 我尝试过使用as Array<...进行覆盖,但这不起作用,因为AxiosResponse不能转换为Array (例如,没有 .length)。

谢谢!

语境

  • axios 版本:0.16.2
  • 环境:Visual Studio 代码

最有用的评论

我在axios.d.ts中覆盖AxiosResponse #$ :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

所有50条评论

使用axios.request<T>(...args)样式定义。
数组中的最后一个 Response-interceptor 隐式地遵守像(currentResponse: any) => T这样的接口

因此,如果您的data类似于:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

然后你可以说:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

@zcei哦,实际上这对全局拦截器不起作用,例如axios.interceptors.response.use

我认为确实如此 - 拦截器目前是非常“宽松”的类型(又名any ),所以你可以附加响应拦截器并拥有它(r: any): any => r.data (这基本上就像你会在非严格模式下省略任何输入)。

只有在您调用axios.request时,您才能将其配置为泛型,然后将其作为data属性的类型传递。

(实际上我在上面的代码片段中犯了一个错误, data实际上是AxiosResponse<ServerData>它有一个名为.data的字段,类型ServerData - 更新了它)

@zcei嗨,抱歉,但这确实不适用于我的示例,我创建了一个代码框来突出显示此问题。

在这里查看

遇到同样的问题,拦截器基本上可以归结为:

this.instance.interceptors.response.use(response => response.data);

尽管如此,返回类型

this.instance.post<string>(...);

AxiosPromise<string> ,它期望数据被包装。

这是故意的。 您应该在拦截器中转换数据,而不是提升响应键

https://github.com/axios/axios/blob/0b3db5d87a60a1ad8b0dce9669dbc10483ec33da/lib/core/dispatchRequest.js#L63 -L67

我以为你会有像这样的服务器响应

{
  "data": {
    "foo": 42
  }
}

并想摆脱response.data.data嵌套。 如果您在拦截器中返回response.data ,那么您以后可以通过response.data.foo而不是response.data.data.foo访问它。

但是访问response.foo是行不通的,因为这是跟踪其他内容(如响应代码等)的“根”响应级别。

抱歉,但我不同意您的前提,至少在功能方面不同意。
如果不使用拦截器来解包数据属性,我将不得不这样做

this.instance.post<string>(...).then(response => response.data);
this.instance.get<number>(...).then(response => response.data);

在我定义的_每个_端点中。 所有这些代码重复有什么意义?

每当请求成功时,有then意味着,我并不真正关心响应代码等。我只关心响应代码,如果某些东西_没有_工作,这就是错误处理程序的用途,无论是在拦截器上还是在特定端点上。

this.instance.interceptors.response.use(response => {
  // Everything went well, pass only relevant data through
  return response.data;
}, error => {
  // Something went wrong, figure out how to handle it here or in a `.catch` somewhere down the pipe
});

每当请求成功时,有 then 意味着,我并不真正关心响应代码

好吧,可能不关心任何其他信息,但是限制每个人使用您只关心主体的HTTP 客户端并不是真正的解决方案。
即使调用成功,也有足够多的合法用例用于状态代码和标头。 就像区分204200 ,检查速率限制标头,提取Link标头以获取其他资源(分页)等。

如果你不在乎,把 Axios 包起来,扔掉所有东西:

this.instance.request = <T>(...args) => {
  return axios.request<T>(...args).then(({ data }) => data)
}

this.instance.request<string>({ method: 'post', ... }).then(data => { ... });
this.instance.request<number>({ method: 'get', ... }).then(data => { ... });

我不明白你从哪里得到这样一种观念,即每个人都应该像我在一个项目中那样做事。 我只是想解决一个如何在每个请求上自动解包数据的问题,鉴于这个问题就在我面前,我不是唯一一个遇到这个问题的人。

拦截器_似乎_喜欢做这件事的正确地方,无论是他们的名字还是他们在自述文件中的描述(对每个响应都做一些事情)。 处理204200等然后传递数据在拦截器中也是有意义的,因为这样你就可以在一个中心位置完成它。

在我看来,直观的解决方案是从拦截器返回你想要的任何东西,而不是让库以单一方式强制执行。

如果要转换数据中的某些字段:

this.instance.interceptors.response.use(response => {
  response.data.foo = JSON.parse(response.data.bar);
  return response;
});

如果你只想解开 Axios 的数据:

this.instance.interceptors.response.use(response => response.data);

这将使开发人员可以选择做什么,在我看来,这比一个非常固执己见的拦截器要好。 当然,你可以不同意。

你在推理“所有代码重复的重点”,所以我只是试图解释为什么这些信息是必要的。

如果任何拦截器可以在没有通用结构的情况下扰乱整个响应,我真的无法找到一种方法来保持拦截器和响应中的类型安全。 :/
我猜一个请求的响应类型需要变成any并把精力放在开发人员手中,以确保他们做正确的事情。

对我来说,由于轻松的打字,这至少听起来比访问一个属性更容易出错。

如果您能提出一个 PR,允许拦截器覆盖根响应,同时保持“80% 用例”的类型安全,我会非常高兴!

问题是,我制作的上述两个示例都已经按照我描述的方式工作,功能方面,只是类型错误。 如果在拦截器中返回(修改后的)响应之外的任何其他内容都是错误的,我认为在那里更新预期的类型会很好。

首先,知道你在做同样的事情很有趣@Etheryte

在我们的应用程序中, r => r.data是链中的最终响应拦截器,我们使用其他依赖状态码来处理刷新令牌等的拦截器,但在全局级别,因为我们不需要针对特定​​的处理来电。

我知道可能无法使用 TS 来适应这个用例。 但是, @zcei不可否认,这是使用 Axios 的合法方式,因为它使用的是 Axios API 的官方部分(拦截器):)。

嗬! 我一直在查看错误的代码🤦‍♂️,并且在transformResponse部分,而不是拦截器 - 非常抱歉!

您将如何更新类型以说明拦截器更改返回类型?

也许是这样的?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

你会这样称呼它:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

刚刚在本地尝试过,似乎可以满足您的需求。
也许我可以在晚上一起获得 PR 来更新所有方法。

@zcei看起来不错! 默认使用 AxiosResponse 当然在 99% 的情况下都是有意义的 👍

很高兴听到! 再次为我的困惑感到抱歉😓

@zcei没问题! 出于兴趣,Axios 的发布周期是什么?

没有 - 我仍然有一些关于 v1 alpha (#1333) 的未决事项,与此同时, @nickuraltsev / @emilyemorehouse会在必要时发布。
自从v0.18以来,已经有了一些吸引力(包括我最喜欢的:实例的范围选项),所以我想他们可以取消发布。 有关更多信息,我只想邀请您到Gitter ,以便我们保持问题的重点🙂

我一直希望发布 0.19 版本,但最后我检查了一下,master 未能通过 CI。 一旦我们获得 1.0 版本,我肯定会想要一个更可靠/更常规的发布时间表。

遇到同样的问题。 有什么解决办法吗?

@qidaneix您可以尝试安装npm install axios/axios#master直到发布。 #1605 应该已经修复了。 很高兴得到一些反馈,这是否真的有助于现实生活中的用例而不仅仅是测试🙂

@zcei我明天试试

@zcei请问什么时候发布?

+1 1.0.0 何时发布?

这肯定是 1.0 之前的版本。 @Khaledgarbaya您是否也被 Matt 添加到 NPM 中? 否则我们需要善待其他发布它的维护者😉

大家好。 我目前正在管理 NPM 版本。 我很想发布另一个 1.0.0 之前的版本,但是 Travis 测试失败了,我还没有机会修复它们。 一旦他们修好了,我很高兴立即把它弄出来😬

@zcei我没有被添加到 npm 仓库中,我只能合并更改

这个问题有什么更新吗,在不久的将来会有版本吗,比如第二个月?

+1

❤️如果现在可以发布

我们知道,但目前已被阻止。 🙁 你可以在这里获取更多信息: https ://github.com/axios/axios/issues/1657#issuecomment -410766031

作为 0.19.0-beta.1 的一部分发布。

这可以使用npm install [email protected]npm install axios@next安装

也许是这样的?

interface AxiosInstance {
  request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}

你会这样称呼它:

axios.request<User, string>({
  method: 'get',
  url: '/user?id=12345'
})
  .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
    console.log(foo)
  })

刚刚在本地尝试过,似乎可以满足您的需求。
也许我可以在晚上一起获得 PR 来更新所有方法。

User(T, the first generic param) 好像没用过,如果我想使用自定义返回类型,我看起来很奇怪😕

axios.request<void, string>({
    ...
})

使用 void 可能更清楚。

@emilyemorehouse 听起来并不忘恩负义,但 0.19-beta 版已经开放了三个月,GA 版本是否有预计到达时间? 我们的项目有一个问题,需要这些修复之一。 我在 Gitter 频道中问过这个问题,但维护人员似乎没有在那边回应......

也许更好的措辞是在发布之前需要解决哪些问题,是否有地方可以跟踪它? 由于似乎有很多兴趣,分散工作并清楚地了解所需的工作可以帮助加快速度。

0.19 已经有一个项目里程碑,但列出的票数几个月来没有任何变化。

const func1: any = () => { return axios.request(...) }

我在axios.d.ts中覆盖AxiosResponse #$ :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

顶撞这个问题。 它工作......好吧,当我刚刚将 AxiosInstance 定义复制到本地类型时,但在我看来,实现的解决方案非常冗长,除非我做错了什么(不是 Typescript 专家)。 由于我使用的是使用axios.create创建的单独 axios 实例并使用此拦截器:

AxiosClient.interceptors.response.use(
  response => response && response.data,
  error => error && error.response && error.response.data
);

其中response.data总是具有这种形式:

export interface APIResponse<T = any> {
  data: T;
  message: string;
  status: boolean;
}

看来我必须像这样使用AxiosClient.post

AxiosClient.post<EndpointAPIResponse, APIResponse<EndpointAPIResponse>>('/endpoint', { someData });

.then中有正确的类型。 我在这里做错了什么还是真的应该那么冗长? 如果没有,如果我可以在创建实例时传递预期的响应模式会更好,但我不能让它工作:

export interface AxiosInstance<R> {
  <T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  <T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  defaults: AxiosRequestConfig;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<R>;
  };
  getUri(config?: AxiosRequestConfig): string;
  request<T = any>(config: AxiosRequestConfig): Promise<R<T>>;
  get<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  head<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
  patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
}

export interface AxiosStatic extends AxiosInstance<AxiosResponse> {
  create<T = AxiosResponse>(config?: AxiosRequestConfig): AxiosInstance<T>;
  Cancel: CancelStatic;
  CancelToken: CancelTokenStatic;
  isCancel(value: any): boolean;
  all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
  spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
}

它适用于没有泛型类型的axios.create()或只是axios ,但如果我像这样传递我的界面:

const AxiosClient = axios.create<APIResponse>({
  // ...
});

然后像这样使用它: AxiosClient.post<string>('/endpoint').then(value => value.data) , value.data有类型...最终以某种彻底的灾难告终。 谁能帮我解决这个问题?

编辑:好的,我想它不会起作用,因为不可能以这种方式使用泛型(所以R<T>当 R 是泛型类型时语法不正确,但我猜 WebStorm 出于某种原因没有突出显示对我来说); https://github.com/Microsoft/TypeScript/issues/1213我认为这会解决它,但不知道它是否会被实施。

const request = <T>(options: AxiosRequestConfig) => {
    return axios.request<IResponse<T>>(options).then<T>(response => {
        const data = response.data;
        if (data.c !== '0') {
            return Promise.reject(new Error(data.m || 'error'));
        }
        return data.d;
    });
}

采用:

request<IApiGetBrandGoodsInfoByIds>({
        method: 'GET',
        url: '/api/a/b',
    });

@zcei这个问题解决了吗? 我无法让以下工作:

// so I created an axios instance:
const instance = axios.create(someOptions);

// then used interceptors
instance.interceptors.response.use((res) => res.data.data);   // server response will always have 'data'

// then when using the following to make a request
instance.get<string>(someURI);  // suppose server response was {data: 'some message'}

// ^^ the above returns type AxiosPromise<string>. How do I get it to return type Promise<string> instead?

有同样的问题,我安装的是0.19beta版,ts也无法解析正确的类型

我在axios.d.ts中覆盖AxiosResponse #$ :

import axios from 'axios'

declare module 'axios' {
  export interface AxiosResponse<T = any> extends Promise<T> {}
}

哦牛皮

我使用拦截器执行以下操作: response => response.data 。 所以我需要完全删除AxiosResponse包装器。

我最终得到:

import axios from 'axios'

declare module 'axios' {
  export interface AxiosInstance {
    request<T = any> (config: AxiosRequestConfig): Promise<T>;
    get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
    post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
    patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  }
}

如果有人不知道如何使用它:

  1. 将类型写入某处,例如src/types/axios/axios.d.ts
  2. 更新您的tsconfig.json ,例如
{
  "compilerOptions": {
    "typeRoots": [
        "./node_modules/@types",
        "./src/types/",
    ]
  }
}

这对我有用:

Api.boards.createBoard = jest.fn((name:string) => {
      const mockBoardResult = {
        created_on: mockBoardData.date,
        name,
        threads: [],
        updated_on: mockBoardData.date,
        _id: mockBoardData._id
      };
      return Promise.resolve({data:mockBoardResult}) as Promise<AxiosResponse<any>>
    });

另一种覆盖的方法

import * as axios from 'axios'

declare module 'axios' {
  interface AxiosInstance {
    (config: AxiosRequestConfig): Promise<any>
  }
}

我认为我们可能不应该在使用拦截器时向AxiosResponse添加属性,因为拦截器可以被弹出。

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default addFooToResp
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

var id = axios.interceptors.response.use(addFooToResp)

axios.get('xx').then(res => {
  // have type defined
  // because we use `declare module 'axios'` ts can infer type
  console.log(res.foo)
})

// but if then eject?
axios.interceptors.response.eject(id)
axios.get('xx').then(res => {
  // also have type defined, it's maybe not reasonable
  console.log(res.foo)
})

为什么我们使用打字稿? 因为我们希望我们的项目是安全的。
如果有一天我们从基本 utils 中删除一个属性,我们希望引用它的代码在编译时产生错误。

所以,我们想用拦截器AxiosResponse添加一些属性并进行类型推断,这是矛盾的,因为没有办法确保在拦截器被弹出时可以更新类型推断

我认为 axios 应该告诉用户:
拦截器应该处理对AxiosResponse没有影响的东西,如果你想扩展AxiosResponse属性并进行类型推断,你应该喜欢'plugin'

// @axios-exts/add-foo-to-resp
declare module 'axios' {
  interface AxiosResponse<T = any> {
    foo: string
  }
}
const addFooToResp = (res: AxiosResponse) => {
  res.foo = 'bar'
  return res
}
export default (axios) => {
  axios.interceptors.response.use(addFooToResp)
}
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'

addFooToResp(axios)

这不是 100% 安全,但它比仅使用axios.interceptors.response.use更安全

我推荐 axios 设计一个插件系统,现在我们总是看到

import axios from 'axios'
import wrapper from 'axios-cache-plugin'

let http = wrapper(axios, {
  maxCacheSize: 15
})
export default http

使用like wrapper连接插件到axios,每个插件没有通用规则,不够优雅。 我想应该喜欢

import axios from 'axios'
import axiosCache from 'axios-cache-plugin'

axios.use(axiosCache, {
  // some options
})
export axios
// axios-cache-plugin
export default (axios) => {}
// or
export default {
  install(axios){}
}
// like Vue.use for Vue.js

使用axios.request<T>(...args)样式定义。
数组中的最后一个 Response-interceptor 隐式地遵守像(currentResponse: any) => T这样的接口

因此,如果您的data类似于:

interface ServerResponse {
  data: ServerData
}

interface ServerData {
  foo: string
  bar: number
}

然后你可以说:

axios.request<ServerData>({
  url: 'https://example.com/path/to/data',
  transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
  // `response` is of type `AxiosResponse<ServerData>`
  const { data } = response
  // `data` is of type ServerData, correctly inferred
})

请告诉我如何使用 tsx 在 Axios 请求中添加标头和配置

@MoZiadAlhafez

declare module 'axios' {
  interface AxiosRequestConfig {
    useCache: boolean
  }
}

但这不推荐

这是一个很长的故事,但似乎已经在#1605 中解决了。 请参阅https://github.com/axios/axios/issues/1510#issuecomment -396894600。

如果我误解了某些东西,最好打开一个新问题并参考此处。 谢谢。

此页面是否有帮助?
0 / 5 - 0 等级