Мы все использовали какую-то систему типов в нашем коде, здесь я буду говорить о TypeScript, языке программирования со строгой типизацией, который преобразуется в JavaScript.
За последние несколько лет он превратился в нечто большее, чем просто статические типы, и стал тем, что мне нравится называть интеллектуальной типизацией. У него очень мощная система типов, потому что она позволяет выражать типы через другие типы, может выводить типы из логики программы и, таким образом, может обнаруживать больше проблем с типами.
Эта статья в основном посвящена расширенным функциям TypeScript и его использованию.
Если вы еще не играли с Typescript, я бы посоветовал вам проверить игровую площадку TS.
У вас много лишних типов? Вы хотите, чтобы ваш набор текста был более удобным для повторного использования? Вы хотите больше безопасности типов? И, в свою очередь, написать более удобный для сопровождения код? Ниже я перечислю некоторые расширенные функции машинописного текста, которые помогут вам в этом.
Оператор keyof
type
Оператор keyof принимает тип объекта и создает строку или числовое буквальное объединение его ключей.
Следующий тип координат имеет тот же тип, что и «x» | «у»:
type Point = { x: number; y: number }; type Coordinates = keyof Point; // ‘x’ | ‘y’
У этого может быть много вариантов использования, например, самый очевидный из них — убедиться, что вы передаете только действительные ключи при доступе к свойству объекта.
function getCoordinate(coordinate: keyof Point, point: Point) { return point[coordinate] } // will give error as key 'z' doesn't exists on Point type getCoordinate('z')
Оператор typeof
type
Оператор typeof, который можно использовать в контексте type для ссылки на type переменной или свойства
const evenOrOdd = (num: number) => num % 2 === 0 ? “even” : “odd” type EvenOrOdd = ReturnType<typeof evenOrOdd> // “even” | “odd”
Если мы внесем изменения в возвращаемый тип EvenOrOdd, тип EvenOrOdd последует за его изменениями.
Типы индексированного доступа
Мы можем использовать индексированный тип доступа для поиска определенного свойства в другом типе.
type Person = { age: number; name: string; alive: boolean } type Age = Person[“age”]; // number
В приведенном выше примере вы можете видеть, что мы создали новый тип Age, обратившись к свойству age типа Person, что очень похоже на доступ к значению ключа из объекта javascript.
Тип индексирования сам по себе является типом, поэтому мы можем использовать объединения, keyof или другие типы в качестве индексов.
type AgeOrName = Person[“age” | “name”]; // string | number // below type will resolve to string | number | boolean type PersonPropertiesType = Person[keyof Person];
Условные типы
Условные типы помогают описать взаимосвязь между типами входных и выходных данных.
Синтаксис:SomeType расширяет OtherType ? TrueType : FalseType;
type TimeOrString = Park extends Verb ? Park[“time”] : Park[“name”] // with generics type MessageOf<T extends { message: unknown }> = T[“message”]; interface Email { message: string; } type EmailMessageContents = MessageOf<Email>;
С помощью универсальных типов мы можем указать условия для того, какие типы разрешено передавать универсальному типу, здесь, в приведенном выше примере, тип MessageOf принимает только тип, который расширяет тип { message: unknown } (имеет свойство сообщения).
Передача любого другого типа приведет к сообщению об ошибке.
// error: Property ‘message’ is missing in type ‘Person’ type PersonMessage = MessageOf<Person>;
Дженерики
Одним из основных инструментов в наборе инструментов для создания повторно используемых компонентов являются универсальные шаблоны. Универсальные шаблоны позволяют создать компонент, который может работать с различными типами, а не с одним. Это позволяет пользователям потреблять эти компоненты и использовать их типы.
// not generic type List = { items: string[] } // bad type List = { items: any[] // using any type } function renderList(list: List) { ... } // not generic render
используя тип any, вы теряете безопасность типов и подвергаете себя проблемам, которые трудно отследить, поэтому лучшим вариантом будет использование дженериков.
type List<T> = { items: T[] } function renderList<T>(list: List<T>) { … }
Сопоставленные типы
Когда вы не хотите повторяться, иногда тип должен быть основан на другом типе. Сопоставленный тип — это универсальный тип, который использует объединение PropertyKeys (часто создается через keyof) для перебора ключей для создания типа.
Предположим, у нас есть тип Todos
type Todos = { eat: Eat, sleep: Sleep, code: Code }
Теперь мы хотим иметь другой тип, который представляет статус задачи, поэтому в основном у нас есть все задачи с логическим флагом, чтобы проверить, выполнена ли задача или нет. Что будет выглядеть примерно так, как показано ниже
type TodoStatus = { eat: boolean, sleep: boolean, code: boolean, }
Но то же самое можно написать и с использованием типа Mapped, который более лаконичен и не нуждается в обновлении, если вы решите добавить больше свойств в свой тип Todo.
type TodoStatus = { [Property in keyof Todos]: boolean }
Переназначение ключей через as
В TypeScript 4.1 и более поздних версиях вы можете переназначать ключи в сопоставленных типах с предложением as a в сопоставленном типе.
type Person2 = { name: string, age: number, canBackflip: boolean, } type MappedTypeToNew<Type> = { [Properties in keyof Type as 'newProperty']: Type[Properties] }
В приведенном выше примере MappedTypeToNew пересопоставляет все ключи переданного Type со строкой 'newProperty'.
Поэтому, если мы затем передадим Person в MappedTypeToNew, это приведет к типу ниже, что означает, что все ключи переименованы в newProperty, а затем типы также объединены, чтобы иметь один тип объединения всех типов ключей «строка | номер | логический ”
{ newProperty: string | number | boolean; }
Некоторые более сложные варианты использования сопоставленных типов с переназначением «as» могут быть чем-то вроде создания сопоставленного типа со всеми методами получения объекта. Таким образом, наш тип получателя может быть чем-то вроде
// adds a get prefix to all the properties and capitalizes the first char type Getters<T> = { [Property in keyof T as `get${Capitalize<string & Property>}`]: () => T[Property] }
Затем, если вы хотите создать тип Getter для какого-либо типа, вы можете сделать что-то вроде следующего:
type PersonGetters = Getters<Person2>
что эквивалентно написанию
type PersonGetters = { getName: () => string; getAge: () => number; getCanBackflip: () => boolean; }
Литеральные типы шаблонов
Они имеют тот же синтаксис, что и строки шаблонных литералов в JavaScript, но используются в позициях типа.
type Width = `${number}px`; // will accept values like ‘1px’, ‘2px’ // possible types for a chess game type Columns = ‘a’ | ‘b’ | ‘d’ | ‘e’ | ‘f’ | ‘g’ | ‘h’ type Rows = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 type Cell = `${Columns}${Rows}` // “a1” | “a2” | “a3” | “a4” | …
Outro
Есть еще много вещей, которые нужно исследовать. Мне очень нравится использовать Утилиты. Они охватывают наиболее распространенные варианты использования, с которыми вы можете столкнуться, но вышеупомянутые функции также очень полезны и могут помочь вам написать более надежный, пригодный для повторного использования и типобезопасный код.
Рекомендации
Заинтересованы в присоединении к нашей команде? Просмотрите наши открытые вакансии или посмотрите, чем мы занимаемся на HeyJobs.