Я пишу обертку fetch()
, которая принимает аргумент параметров. Обычно эта оболочка использует верблюжий регистр возвращаемого значения, но если для параметра dontCamelize
установлено значение true, тогда она должна обернуть возвращаемое значение в общий Decamelize<>
. Однако я не уверен, как выразить это в TypeScript.
Поскольку свойств параметров может быть несколько, я не думаю, что смогу использовать переопределения функций. Я пытался выразить это с помощью помощника ResponseType и условного типа, но, похоже, это не сработало. Любая помощь очень ценится!
Этот код генерируется с помощью инструмента генерации кода спецификации OpenAPI, который всегда передает первый параметр типа, поэтому он всегда будет выглядеть следующим образом:
const res = fetcher<{fooBar: 'baz'}>({ fooBar: 'baz' }); // Expect: { fooBar: 'baz' }
type Decamelize<T> = {
[K in keyof T as K extends string ? `${Uncapitalize<K>}` : K]: T[K]
};
type Options = {
dontCamelize?: boolean;
verbose?: boolean;
// Add more options here as needed
option1?: string;
option2?: number;
option3?: boolean;
};
type ResponseType<T, O extends Options> =
O['dontCamelize'] extends true ? Decamelize<T> : T;
export function fetcher<T, O extends Options = {}>(
subject: T,
options: O = {} as O
): ResponseType<T, O> {
return subject as any;
}
// Usage examples
const res1 = fetcher<{ fooBar: 'baz' }>({ fooBar: 'baz' }); // Expect: { fooBar: 'baz' }
const res2 = fetcher<{ fooBar: 'baz' }>({ fooBar: 'baz' }, { dontCamelize: true }); // Expect: Decamelize<{ fooBar: 'baz' }>
const res3 = fetcher<{ fooBar: 'baz' }>({ fooBar: 'baz' }, { verbose: true }); // Expect: { fooBar: 'baz' }
const res4 = fetcher<{ fooBar: 'baz' }>({ fooBar: 'baz' }, { dontCamelize: true, verbose: true }); // Expect: Decamelize<{ fooBar: 'baz' }>
Удалите параметр явного типа, и все заработает. Когда вы указываете один, TS использует значение по умолчанию для другого. Если вы удалите их все, они выведут их все, включая объект параметров.
@Darryl Noakes - да, dontCamelize - единственный вариант, который меняет тип ответа
@DarrylNoakes Я не уверен, о каком явном параметре типа вы говорите. Вы не против опубликовать ответ?
о, я думаю, вы говорите о том, где я вызываю сборщик <{ fooBar: 'baz' }>(...) Вы правы, это работает, если я это удалю, но, к сожалению, мне нужно передать тип в позвони на сайт вот так. Фактический код, с которым я работаю, передаст этот тип на другой уровень утилите внутри сборщика. Он генерирует код из HTTP-клиента OpenAPI, и я несколько ограничен в его структуре.
Правильный. Завтра попробую написать ответ, если еще никто не ответил
Я не думаю, что в настоящее время это возможно без изменения кода вызова. См. Предложение: вывод аргумента частичного типа, которое похоже на вашу проблему. Это требует от вас явного запроса вывода дополнительных параметров. Там могут быть обходные пути.
Пожалуйста, уточните: (1) Можете ли вы изменить код сайта вызова или нет? (2) Вы пишете функцию fetcher
и вызываете внутри нее сгенерированный код?
Если вы пишете это и вам просто нужно передать тип функции, вызываемой внутри, вывод должен быть в порядке. Но я не думаю, что это так?
Рабочая версия с использованием перегрузок: tsplay.dev/NBKZpm
Спасибо @DarrylNoakes, это выглядит великолепно!
Удалите явные общие параметры <{ fooBar: 'baz' }>
:
const res1 = fetcher({ fooBar: 'baz' });
const res2 = fetcher({ fooBar: 'baz' }, { dontCamelize: true });
const res3 = fetcher({ fooBar: 'baz' }, { verbose: true });
const res4 = fetcher({ fooBar: 'baz' }, { dontCamelize: true, verbose: true });
Как указано в комментариях после того, как вы опубликовали свой ответ и позже отредактировали вопрос, этот подход невозможен.
Вам не нужно его передавать, вы можете использовать ResponseType<T, O>
внутри функции fetcher
. А если оно находится за пределами, оно вам не нужно.
Сайт вызова должен иметь вид const res2 = fetcher<{ fooBar: 'baz' }>({ fooBar: 'baz' }, { dontCamelize: true })
с включенным явным параметром типа. Этот код не находится под контролем ОП, поэтому его нельзя удалить. Если ОП правильно объяснил.
Тогда ты мало что сможешь сделать. Я бы попробовал использовать что-то вроде ts-morph.com, чтобы найти узлы вызова функции с именемfetcher
и удалить параметры типа из узла вызова функции. Я бы выполнил этот шаг перед шагом сборки TSC по умолчанию.
Да, к сожалению, необходимо передать первый параметр типа. Код генератора выдает что-то вроде: httpClient<GetBillsResponse>( { url:
/bills, method: 'GET', params }, options )
Я контролирую только реализацию объекта httpClient.
Пока существует только один* параметр, влияющий на тип возвращаемого значения, вы можете использовать перегрузки.
* Ну, можно было бы и больше, но вам придется указать перегрузку для каждой комбинации. Два — это практический максимум.
(1) Измените тип Options
на общий.
Первый шаг — добавить параметр универсального типа к объекту параметров, чтобы мы могли различать значения dontCamelize
.
Измените это:
type Options = {
dontCamelize?: boolean
}
на это:
type Options<dontCamelize extends boolean = boolean> = {
dontCamelize?: dontCamelize
}
(2) Измените ResponseType
, чтобы использовать параметр логического типа вместо типа «Параметры».
Измените это:
type ResponseType<T, O extends Options> =
O['dontCamelize'] extends true ? Decamelize<T> : T
на это:
type ResponseType<T, dontCamelize extends boolean> =
dontCamelize extends true ? Decamelize<T> : T
(3) Функция изменена с использования параметра универсального типа на перегрузки.
Используемая перегрузка зависит от типа dontCamelize
в переданном объекте параметров, что позволяет нам установить тип возвращаемого значения на его основе.
Изменения:
options
.dontCamelize
было ложным или не указано, и одну для того, чтобы оно было истинным.ResponseType
) передается соответствующий логический тип для dontCamelize
.Измените это:
export function fetcher<T, O extends Options = {}>(
subject: T,
options: O = {} as O
): ResponseType<T, O>
на это:
// Blank lines added for readability. They're optional.
// Overload signature: `dontCamelize` is not specified or false
export function fetcher<T>(
subject: T,
options?: Options<false>
): ResponseType<T, false>
// Overload signature: `dontCamelized` is true
export function fetcher<T>(
subject: T,
options: Options<true>
): ResponseType<T, true>
// Implementation signature
export function fetcher<T>(
subject: T,
options: Options = {}
): ResponseType<T, boolean>
Является ли
dontCamelize
единственным параметром, влияющим на тип возвращаемого значения?