Chupurnov Valeriy
Chupurnov Valeriy
Front End Engineer
Назад

TypeScript правильная перегрузка функций

TypeScript правильная перегрузка функций

Если вы работали с TS, то вам должна быть знакома такая конструкция:

function double(a: string): string; function double(a: number): number; function double(a: any) { return a + a; }
Copy

Это механизм оверайда или перегрузки объявления типа функций. Таким же образом можно перегружать и методы классов. Это очень удобно, мы точно сообщаем 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; } }
Copy

Такой код позволяет писать интересные конструкции:

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'); })
Copy

Так, с перегрузкой разобрались.

Так что же не так с первым примером?

Это объявление плохо себя ведет при таком использовании:

const a: string | number = someFunc(); double(a); // Тут будет ошибка
Copy

Наша реализация типов умеет принимать типы отдельно, но не их объединение. В книге приводится один из вариантов решения. Так называемые дженерики:

function double<T>(a: T): T;
Copy

Я с удивлением узнал, что такой код является устаревшим и так писать не стоит. Казалось бы, идеально? Какой тип пришел, такой и уйдет. Нет, не идеально.

const v = double('a'); // v имеет тип не string, а более узкий 'a'
Copy

На помощь придут тернарные выражения из мира TypeScript или условные типы:

function double<T extends string | number>(a: T): T extends number ? number : string;
Copy

Теперь все работает, как и задумано.

Всем добра!