Рассмотрим следующую функцию TypeScript:
function example<T>(
...[callback, batch]:
| [callback: (param: T) => void, batch?: false]
| [callback: (param: T[]) => void, batch: true]
) {
if (batch) {
callback([]);
} else {
callback({} as T);
}
}
Определение типа callback
указывает тип аргумента, поэтому можно было бы ожидать, что тип аргумента будет выведен в следующих вызовах:
example<string>((param) => {}); // Param should be inferred as string
example<string>((param) => {}, true); // Param should be inferred as string[]
Однако TypeScript жалуется, что не может определить тип param
:
error TS7006: Parameter 'param' implicitly has an 'any' type.
example<string>((param) => {});
~~~~~
error TS7006: Parameter 'param' implicitly has an 'any' type.
example<string>((param) => {}, true);
~~~~~
Предоставление явного типа решает проблему, но я считаю, что у TypeScript достаточно информации, чтобы сделать вывод самостоятельно. Фактически, TypeScript сообщает о неправильном вводе при предоставлении недопустимого явного типа:
error TS2345: Argument of type '[(param: number) => void]' is not assignable to parameter of type '[callback: (c: string) => void, batch?: false | undefined] | [callback: (c: string[]) => void, batch: true]'.
Type '[(param: number) => void]' is not assignable to type '[callback: (c: string) => void, batch?: false | undefined]'.
Type at position 0 in source is not compatible with type at position 0 in target.
Type '(param: number) => void' is not assignable to type '(c: string) => void'.
Types of parameters 'param' and 'c' are incompatible.
Type 'string' is not assignable to type 'number'.
example<string>((param: number) => {});
~~~~~~~~~~~~~~~~~~~~~
error TS2345: Argument of type '[(param: number[]) => void, true]' is not assignable to parameter of type '[callback: (c: string) => void, batch?: false | undefined] | [callback: (c: string[]) => void, batch: true]'.
Type '[(param: number[]) => void, true]' is not assignable to type '[callback: (c: string[]) => void, batch: true]'.
Type at position 0 in source is not compatible with type at position 0 in target.
Type '(param: number[]) => void' is not assignable to type '(c: string[]) => void'.
Types of parameters 'param' and 'c' are incompatible.
Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
example<string>((param: number[]) => {}, true);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Есть ли способ заставить это работать без явного типа аргумента?
@jcalz Это действительно та же проблема! Спасибо, что нашли время сообщить об этом в официальный репозиторий TypeScript 😄 Вероятно, перегрузки помогут. Кстати говоря, можете ли вы использовать их в функциональном члене объекта?
Я напишу ответ, когда у меня будет возможность. Я не понимаю, что «можно ли использовать перегрузки в функциональном члене объекта?» значит точно. Вы определенно можете определять объекты с перегруженными сигнатурами вызовов методов, но реализация может быть сложной (см. ms/TS#47669)... однако это не мешает вам их писать. Похоже, это дополнительный вопрос, который не совсем подходит для раздела комментариев SO.
В настоящее время это нереализованная функция TypeScript, описанная по адресу microsoft/TypeScript#42987 . Функция, которая принимает rest параметр типом которого является объединение типов кортежей , ведет себя почти как перегруженная функция с несколькими сигнатурами вызовов. Действительно, если вы начнете называть это:
declare function example<T>(
...[callback, batch]:
| [callback: (param: T) => void, batch?: false]
| [callback: (param: T[]) => void, batch: true]
): void
Среды IDE с поддержкой IntelliSense покажут вам списки завершения, которые выглядят точно так же, как перегрузки:
example(⎀
// ^ 1/2 example(callback: (param: unknown) => void, batch?: false | undefined): void
// ^ 2/2 example(callback: (param: unknown[]) => void, batch: true): void
К сожалению, как вы заметили, они ведут себя не совсем как перегрузки:
example<string>((param) => { }); // err
// ~~~~~ implicit any
example<string>((param) => { }, true); // err
// ~~~~~ implicit any
Если вы перепишете эту функцию, чтобы она была настоящей перегрузкой:
declare function example<T>(callback: (p: T) => void, batch?: false): void;
declare function example<T>(callback: (p: T[]) => void, batch: true): void;
Тогда вы получите желаемое поведение:
example<string>((param) => { });
// ^? (parameter) param: string
example<string>((param) => { }, true);
// ^? (parameter) param: string[]
Это прискорбно, поскольку объединения остальных параметров с типом кортежа могут быть намного компактнее, чем перегрузки с несколькими сигнатурами вызовов, и их легче генерировать программно. Но до тех пор, пока не будет реализован microsoft/TypeScript#42987, я бы предложил писать перегрузки, если вы хотите такое поведение для вызывающих абонентов.
Вы можете использовать перегрузку функций
function example<T>(callback: (param: T) => void, batch?: false): Array<unknown>
function example<T>(callback: (param: T[]) => void, batch: true): {}
function example() {
const [callback, batch] = arguments;
if (batch) {
callback([]);
} else {
callback({});
}
}
Это позволяет вам объявить несколько отдельных способов использования функции, не смешивая их объявления.
Спасибо за ваш вклад @ug_! Функции по-прежнему необходимо объявлять параметры, но перегрузка — это именно то решение, которое я искал!
Вы нажали ms/TS#42987 ; вы хотите, чтобы это вело себя точно так же, как перегрузки со стороны вызывающего абонента. По этой ссылке на игровую площадку показана перегруженная версия, которую я, вероятно, и предложил бы в качестве обходного пути для звонящих. Это полностью отвечает на ваш вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?