TypeScript правильная перегрузка функций
Если вы работали с TS, то вам должна быть знакома такая конструкция:
function double(a: string): string;
function double(a: number): number;
function double(a: any) {
return a + a;
}
Это механизм оверайда или перегрузки объявления типа функций. Таким же образом можно перегружать и методы классов.
Это очень удобно, мы точно сообщаем TS что если подать в функцию number
, то она вернет number
.
Так что же не так с этим примером?
Все еще читаю книгу Эффективный Typescript, и там в главе 6 об этом рассказано. А я делюсь с вами и за одним закрепляю материал в своей голове.
Для начала небольшой ликбез:
Зачем вообще нужна перегрузка?
Это удобно для полиморфных функций, которые могут работать по разному с разным количеством аргументов.
К примеру eventEmmiter в моем редакторе Jodit поддерживает такой вызов навешивания обработчиков событий:
class EventEmmiter {
on(eventName: string, handler: Function): this;
on(target: HTMLElement, eventName: string, handler: Function): this;
on(targetOrEventName: string | HTMLElement, eventNameOrFunction: string | Function, handler?: Function): this {
const eventName = typeof eventNameOrFunction === 'string' ? eventNameOrFunction : targetOrEventName;
const target = typeof targetOrEventName !== 'string' ? targetOrEventName : window;
const handler = typeof eventNameOrFunction === 'function' ? eventNameOrFunction : handler;
target.addEventListener(eventName, handler);
return this;
}
}
Такой код позволяет писать интересные конструкции:
const emmiter = new EventEmmiter();
emmiter
.on('click', (e) => {
alert('Click on Window');
})
.on(document.body, 'click', (e) => {
alert('Click on document');
})
.on(document.querySelector('#root'), (e) => {
alert('Click on the root');
})
Так, с перегрузкой разобрались.
Так что же не так с первым примером?
Это объявление плохо себя ведет при таком использовании:
const a: string | number = someFunc();
double(a); // Тут будет ошибка
Наша реализация типов умеет принимать типы отдельно, но не их объединение. В книге приводится один из вариантов решения. Так называемые дженерики:
function double<T>(a: T): T;
Я с удивлением узнал, что такой код является устаревшим и так писать не стоит. Казалось бы, идеально? Какой тип пришел, такой и уйдет. Нет, не идеально.
const v = double('a');
// v имеет тип не string, а более узкий 'a'
На помощь придут тернарные выражения из мира TypeScript или условные типы:
function double<T extends string | number>(a: T): T extends number ? number : string;
Теперь все работает, как и задумано.
Всем добра!