Typescript перегрузка стрелочных функций
Как уже было сказано ранее, TypeScript, это типизированная надстройка над JavaScript. Другими словами, TypeScript не добавляет никаких новых языковых конструкций (исключением можно назвать конструкцию Enum , который будет рассмотрен чуть позже), а лишь расширяет синтаксис JavaScript при помощи добавления в него типов. По этой причине в этой книге не будут затрагиваться темы, относящиеся к JavaScript, так как она рассчитана на тех, кто уже знаком с его основами.
И прежде чем начать погружение в систему типов TypeScript, нужно рассмотреть такое важное понятие, как указание типа данных, для всех существующих в JavaScript языковых конструкций.
Идеология типизированных языков, определяет правила, по которым указывать типы нужно везде, где это предусмотрено. В TypeScript аннотация типа или указание типа осуществляется с помощью оператора двоеточия : после которого следует идентификатор типа данных. TypeScript является статически типизированным языком, поэтому после того, как идентификатор будет связан с типом, изменить тип будет невозможно.
Синтаксические конструкции var, let, const
При объявлении синтаксических конструкций, объявляемых с помощью операторов var , let и const тип данных указывается сразу после идентификатора.
var identifier: Type = value; let identifier: Type = value; const IDENTIFIER: Type = value;
Функции (function)
При объявлении функции, между её параметрами и телом, указывается тип возвращаемого ею значения. При наличии параметров, тип данных указывается и для них.
function identifier(param1: Type, param2: Type): ReturnedType <>
Не будет лишнем напомнить, что в отличии от JavaScript, в TypeScript, в сигнатуру функции помимо её имени и параметров, также входит и возвращаемое значение.
Помимо этого, в TypeScript можно объявлять параметризированне функции. Функции имеющие параметры типа называются обобщенными (подробнее о них речь пойдет в главе Типы — Обобщения (Generics). Параметры типа заключаются в угловые скобки <> и располагаются перед круглыми скобками () , в которые заключены параметры функции.
function identifier T, U>( ): ReturnedType <>
TypeScript расширяет границы типизации функций и методов таким новым для JavaScript понятием, как перегрузка функций. С помощью перегрузки функций, можно аннотировать функции с одинаковыми идентификаторами, но с различными сигнатурами.
Для этого в первую очередь аннотироют сигнатуры функций, методов или конструкторов, которые не содержат тела.В последнюю очередь объявляют функцию, сигнатура которой совместима со всеми ранее аннотированными версиями.Более подробно эта тема будет освещена позднее.
function identifier(p1: T1, p2: T2): T3; function identifier(p1: T4, p2: T5): T6; function identifier(p1: T, p2: T): T < return 'value'; > class Identifier < constructor(p1: T1, p2: T2); constructor(p1: T3, p2: T4); constructor(p1: T, p2: T) <> identifier(p1: T1, p2: T2): T3 identifier(p1: T4, p2: T5): T6; identifier(p1: T, p2: T): T < return 'value'; >>
Стрелочные Функции (arrow function)
К стрелочным функциям применимы те же правила указания типов данных, что и для обычных функций, за исключением того, что возвращаемый ими тип указывается между параметрами и стрелкой.
( param: Type, param: Type ): Type => value
Классы (class)
Прежде чем продолжить рассмотрение изменений, которые привнес TypeScript в нетипизированный мир JavaScript, хочу предупредить о том, что относительно классов будет использоваться терминология заимствованная из таких языков, как Java или C#, так как она добавляет большей ясности (тем более, что в спецификации TypeScript встречается подобная терминология). Так, переменные экземпляра и переменные класса (статические переменные) в этой книге обозначаются как поля (field). Аксессоры (get, set), обозначаются как свойства (property). А кроме того, поля, свойства, методы, вычисляемые свойства (computed property) и индексируемые сигнатуры (index signature) обозначаются как члены класса (member).
При объявлении поля класса, как и в случае с переменными, тип данных указывается сразу после идентификатора (имени класса). Для методов класса действуют те же правила указания типов данных, что и для обычных функций.
Для свойств, в частности для get указывается тип данных возвращаемого значения. Для set указывается лишь тип единственного параметра, а возвращаемый им тип и вовсе запрещается указывать явно.
Кроме того, классы в TypeScript также могут быть обобщенными. В случае объявления обобщенного класса, параметры типа, заключенные в треугольные скобки, указываются сразу после идентификатора класса.
class Identifier < static staticField: Type = value; // member static get staticProperty(): Type < // member return value; > static set staticProperty(value: Type) < // member > static staticMethod (param: Type, param: Type): Type < >// member [indexSignature: Type]: Type; // member [computedProp]: Type = value; // member field: Type = value; // member get property(): Type < // member return value; > set property(value: Type) < // member > constructor(param0: Type, param1: Type)<> method (param: Type, param: Type): Type < >// member >
Сравнение Синтаксиса TypeScript и JavaScript
Перед тем, как подвести итоги этой главы, не будет лишним собрать обсуждаемое здесь в одном TypeScript коде и сравнить его с аналогичным кодом написанным на JavaScript.
// .ts var identifier: Type = value; let identifier: Type = value; const IDENTIFIER: Type = value; // .js var identifier = value; let identifier= value; const IDENTIFIER = value; // .ts function identifier(param1: Type, param2: Type): ReturnedType <> // .js function identifier(param1, param2) <> // .ts class Identifier < static staticField: Type = value; static get staticProperty(): Type < return value; > static set staticProperty(value: Type) < >static staticMethod (param: Type, param: Type): Type < >[indexSignature: Type]: Type; [computedProp]: Type = value; field: Type = value; get property(): Type < return value; > set property(value: Type) < >constructor(param0: Type, param1: Type)<> method (param: Type, param: Type): Type < >> // .js class Identifier < static staticField = value; static get staticProperty() < return value; > static set staticProperty(value) < >static staticMethod (param, param) < >[computedProp] = value; field = value; get property() < return value; > set property(value) < >constructor(param0, param1)<> method (param, param) < >>
Итог
- Аннотация типа устанавливается оператором двоеточия : после которого следует указание типа данных.
- При объявлении переменных тип данных указывается сразу после идентификатора.
- У функций и методов класса возвращаемый тип данных указывается между параметрами и телом.
- У стрелочных функций возвращаемый тип данных указывается между параметрами и стрелкой.
- У функций, стрелочных функций и методов класса, параметрам также указывается тип данных.
- При необходимости функциям, стрелочным функциям и методам класса можно указать параметры типа, которые заключаются в угловые скобки и указываются перед круглыми скобками, в которых размещаются параметры функции.
- В TypeScript, аннотирование типов у функций, методов и конструкторов, расширено при помощи перегрузки функций.
- Полям класса, тип данных указывается сразу после идентификатора-имени.
- Для геттеров (getters) указывается возвращаемый тип данных.
- Для сеттеров (setters) указывается тип единственного параметра и вовсе не указывается возвращаемый тип.
Краткое руководство по перегрузке функций в TypeScript
Мы постоянно используем функции в наших приложениях, вообще говоря, большая часть JS-кода — это функции. TypeScript предоставляет нам множество способов их описания.
Для базовой функции это сделать довольно просто, но что если функция принимает различные количества и типы аргументов или даже возвращает разные типы в зависимости от того, как она вызывается?
Для таких случаев в TypeScript есть удобная функция перегрузки функций. Давайте посмотрим, как ее использовать и как она может помочь вам улучшить ваш код.
Типизация функций
Я проголодался, поэтому давайте создадим функцию, которая будет готовить блюдо:
function cookDish(ingredient: string): string return `$ingredient>🍴 is ready, bon appétit!`; >
Мы передали один аргумент string в качестве ингредиента, и после вызова функция возвращает блюдо string :
cookDish('🥚'); // '🍳🍴 is ready, bon appétit!'
Но подождите, этого недостаточно, мне определенно нужен сэндвич!
Чтобы приготовить его, нам нужно изменить нашу функцию, чтобы она могла принимать несколько ингредиентов:
function cookDish(ingredient: string | string[]): string const tableSet = '🍴 is ready, bon appétit!'; if (typeof ingredient === 'string') return `$ingredient>$tableSet>`; > else if (Array.isArray(ingredient)) return `$ingredient.join('')>$tableSet>` > throw new Error('Nothing to cook 😭'); >
Мы использовали тип Union для описания аргумента нашей функции, который может быть как одной строкой , так и несколькими строками[] :
cookDish('🌽'); // '🍿🍴 is ready, bon appétit!'; cookDish(['🍞', '🍅', '🥓']); // '🥪🍴 is ready, bon appétit!';
Добавление типов для поддержки различных аргументов — это обычный и хороший подход в большинстве случаев, но иногда вам нужно явно определить все способы вызова функции. Здесь на помощь приходит перегрузка функций.
Перегрузка функций
В случае довольно сложной функции для улучшения удобства использования и читабельности всегда лучше использовать функцию перегрузки функций. Этот подход считается наиболее гибким и прозрачным.
Чтобы использовать его, нам нужно написать несколько сигнатур функций:
- Подпись перегрузки — определяет различные способы вызова функции: аргументы и возвращаемые типы, и не имеет тела. Сигнатур перегрузки может быть несколько (обычно две или более).
- Подпись реализации — обеспечивает реализацию функции: тело функции. Может быть только одна сигнатура реализации, и она должна быть совместима с сигнатурами перегрузки.
Давайте перепишем наш cookDish , используя перегрузку функций:
// Overload signature function cookDish(ingredient: string): string function cookDish(ingredients: string[]): string // Implementation signature function cookDish(ingredient: any): string const tableSet = '🍴 is ready, bon appétit!'; if (typeof ingredient === 'string') return `$ingredient>$tableSet>`; > else if (Array.isArray(ingredient)) return `$ingredient.join('')>$tableSet>` > throw new Error('Nothing to cook 😭'); >
Две сигнатуры перегрузки описывают два разных способа вызова функции: с аргументом string или string[] . В обоих случаях в результате мы получим приготовленное блюдо string .
Подпись реализации определяет поведение функции внутри тела функции.
Однако вызов нашей функции не меняется, мы можем готовить, как и раньше:
cookDish('🍚'); // '🍙🍴 is ready, bon appétit!'; cookDish(['🫓', '🍅', '🥑']); // '🥙🍴 is ready, bon appétit!'; const money: any = 100; cookDish(money); /** ⛔ No overload matches this call. Overload 1 of 2, '(ingredient: string): string', gave the following error. Argument of type 'any' is not assignable to parameter of type 'string'. Overload 2 of 2, '(ingredients: string[]): string', gave the following error. Argument of type 'any' is not assignable to parameter of type 'string[]'. */
Обратите внимание на последний пример выше: деньги не могут приготовить вам блюдо Хотя сигнатура реализации и реализует нашу функцию, ее нельзя вызвать напрямую, хотя она и принимает any в качестве аргумента function cookDish(ingredient: any) . Вы можете вызывать только сигнатуры перегрузки.
Время ужина! Давайте рассмотрим немного более сложный пример:
function cookDinner(dish: string): string function cookDinner(money: number): string // Wrong argument type /** ⛔ This overload signature is not compatible with its implementation signature. */ function cookDinner(dish: string, drink: string, dessert: string): string function cookDinner(dish: string, drink?: string, dessert?: string): string let dinner = `$dish>🍴` if (drink && dessert) dinner += ` $drink>🧊$dessert>🥄` > return `$dinner> is ready, bon appétit!` > cookDinner('🍝'); // '🍝🍴 is ready, bon appétit!'; cookDinner('🍔', '🥤', '🍰'); // '🍔🍴🥤🧊🍰🥄 is ready, bon appétit!'; cookDinner('🍺', '🍸') // 🤒 Invalid number of arguments /** ⛔ No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments. */
Необходимо помнить, что сигнатура реализации должна быть совместима с перегрузками и не может быть вызвана напрямую.
Заключение
Перегрузка функций — это мощная возможность TypeScript, которая позволяет вам более элегантно типизировать ваши функции.
- Перегрузка функций
- Перегрузка функций «за» и «против
- Перегрузка стрелочных функций
Надеюсь, вам понравилось это руководство, следите за новостями.