В проекте, который я перехожу на TypeScript (TS), у меня есть перехватчик ответа r => r.data
. Как мне сообщить TS, что я не ожидаю типа AxiosResponse
? Я пытался переопределить с помощью as Array<...
, но это не работает, поскольку AxiosResponse
нельзя преобразовать в Array
(например, не имеет .length).
Спасибо!
Используйте определение стиля axios.request<T>(...args)
.
Последний Response-перехватчик в массиве неявно соответствует интерфейсу типа (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 привет, извините, но это действительно не работает с моим примером, я создал codeandbox, чтобы выделить эту проблему.
Имея ту же проблему, перехватчик можно свести к следующему:
this.instance.interceptors.response.use(response => response.data);
Несмотря на это, возвращаемый тип
this.instance.post<string>(...);
AxiosPromise<string>
, который ожидает, что данные будут упакованы.
Это намеренно. Вы должны преобразовывать данные в перехватчике, а не поднимать ключи ответа .
Я думал, что у вас будет ответ сервера, например
{
"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-клиент , что вы заботитесь только о теле, на самом деле не является решением.
Существует достаточно законных вариантов использования кодов состояния и заголовков даже при успешных вызовах. Например, различать 204
и 200
, проверять заголовки ограничения скорости, извлекать заголовки 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 => { ... });
Я не понимаю, с чего вы взяли, что все должны делать то, что делаю я, в одном проекте. Я просто пытаюсь решить проблему автоматического развертывания данных при каждом запросе, и, учитывая, что эта проблема была здесь до меня, я не единственный с этой проблемой.
Перехватчики _кажется_ правильным местом для этого, как по их названию, так и по их описанию в файле readme (сделайте что-нибудь с каждым ответом). Обработка 204
против 200
и т. д., а затем передача данных также имеет смысл в перехватчике, потому что тогда вы можете делать это в одном центральном месте.
На мой взгляд, интуитивно понятным решением было бы вернуть то, что вы хотите от перехватчика, а не принудительно использовать библиотеку одним способом.
Если вы хотите преобразовать некоторые поля в данные:
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, поскольку он использует официальную часть API Axios (перехватчики) :).
Дох! Я все время смотрел не тот код 🤦♂️ и был в разделе 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 (#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 , тебя тоже Мэтт добавил в 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 вечером, чтобы обновить это для всех методов.
Пользователь (T, первый общий параметр), кажется, не используется, если я хочу использовать настраиваемые типы возврата, я выгляжу странно 😕
axios.request<void, string>({
...
})
использование void может быть более понятным.
@emilyemorehouse Не хочу показаться неблагодарным, но бета-версия 0.19 открыта уже три месяца, есть ли ETA для выпуска GA? В нашем проекте есть проблема, которая требует одного из этих исправлений. Я спросил об этом на канале Gitter, но похоже, что сопровождающие не отвечают там...
Возможно, лучше было бы сформулировать, какие проблемы необходимо решить перед выпуском, и есть ли место для их отслеживания? Так как, похоже, есть большой интерес, распространение работы и четкое понимание необходимой работы может помочь ускорить процесс.
Уже есть веха проекта для 0.19, но перечисленные билеты не претерпели никаких изменений в течение нескольких месяцев.
const func1: any = () => { return axios.request(...) }
Я переопределяю AxiosResponse
в моем axios.d.ts
:
import axios from 'axios'
declare module 'axios' {
export interface AxiosResponse<T = any> extends Promise<T> {}
}
Подняв этот вопрос. Это работает ... хорошо, когда я просто скопировал определение AxiosInstance в локальные типизации, но реализованное решение, на мой взгляд, очень многословно, если только я не делаю что-то не так (не эксперт по Typescript). Поскольку я использую отдельный экземпляр axios, созданный с помощью axios.create
и использующий этот перехватчик:
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
имеет тип... T. Кроме того, вышеприведенная версия работает только в том случае, если я фактически заменю эти типы в node_modules, в противном случае он полностью перепутается, и я закончиться какой-то полной катастрофой. Может ли кто-нибудь помочь мне с этим?
Изменить: хорошо, я думаю, это не сработает, потому что таким образом невозможно использовать дженерики (поэтому 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 не может проанализировать правильный тип
Я переопределяю
AxiosResponse
в моемaxios.d.ts
: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>;
}
}
Если кто-то не знает, как им пользоваться:
{
"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)
})
почему мы используем машинопись? потому что мы надеемся, что наш проект будет благополучно.
если когда-нибудь мы удалим свойство из базовых утилит, мы хотели бы, чтобы код, который ссылается на него, выдавал ошибку во время компиляции.
Итак, мы хотим использовать перехватчик , добавить некоторое свойство к AxiosResponse
и иметь вывод типа, это противоречиво, потому что нет способа гарантировать, что вывод типа может быть обновлен при извлечении перехватчика.
я думаю, что axios должен сказать пользователю:
перехватчик должен обрабатывать что-то, что не влияет на AxiosResponse
, если вы хотите расширить свойство 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 (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 desgin систему плагинов, теперь мы всегда видим, как
import axios from 'axios'
import wrapper from 'axios-cache-plugin'
let http = wrapper(axios, {
maxCacheSize: 15
})
export default http
используйте как 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-перехватчик в массиве неявно соответствует интерфейсу типа(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 })
Пожалуйста, скажите мне, как я могу добавить заголовок и конфигурацию в запрос Axios с помощью tsx
@MoZiadAlhafez
declare module 'axios' {
interface AxiosRequestConfig {
useCache: boolean
}
}
Но это не рекомендуется
Это длинная история, но, похоже, она была решена в #1605. См. https://github.com/axios/axios/issues/1510#issuecomment-396894600 .
Если я что-то не так понял, лучше открыть новую тему со ссылками сюда. Спасибо.
Самый полезный комментарий
Я переопределяю
AxiosResponse
в моемaxios.d.ts
: