Мы все использовали какую-то систему типов в нашем коде, здесь я буду говорить о 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.