TypeScript правильная перегрузка функций
Если вы работали с TS, то вам должна быть знакома такая конструкция:
Copyfunction double(a: string): string; function double(a: number): number; function double(a: any) { return a + a; }
Это механизм оверайда или перегрузки объявления типа функций. Таким же образом можно перегружать и методы классов.
Это очень удобно, мы точно сообщаем TS что если подать в функцию number
, то она вернет number
.
Так что же не так с этим примером?
Все еще читаю книгу Эффективный Typescript, и там в главе 6 об этом рассказано. А я делюсь с вами и за одним закрепляю материал в своей голове.
Для начала небольшой ликбез:
Зачем вообще нужна перегрузка?
Это удобно для полиморфных функций, которые могут работать по разному с разным количеством аргументов.
К примеру eventEmmiter в моем редакторе Jodit поддерживает такой вызов навешивания обработчиков событий:
Copyclass 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; } }
Такой код позволяет писать интересные конструкции:
Copyconst 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'); })
Так, с перегрузкой разобрались.
Так что же не так с первым примером?
Это объявление плохо себя ведет при таком использовании:
Copyconst a: string | number = someFunc(); double(a); // Тут будет ошибка
Наша реализация типов умеет принимать типы отдельно, но не их объединение. В книге приводится один из вариантов решения. Так называемые дженерики:
Copyfunction double<T>(a: T): T;
Я с удивлением узнал, что такой код является устаревшим и так писать не стоит. Казалось бы, идеально? Какой тип пришел, такой и уйдет. Нет, не идеально.
Copyconst v = double('a'); // v имеет тип не string, а более узкий 'a'
На помощь придут тернарные выражения из мира TypeScript или условные типы:
Copyfunction double<T extends string | number>(a: T): T extends number ? number : string;
Теперь все работает, как и задумано.
Всем добра!