Site logo

Developer Blog

Pavel Koltyshev

Приемы программирования на TypeScript

Содержание

Система типов TypeScript

Дженерики

Синтаксис дженериков

type Generic<T> = {
  payload: T;
};

type StringPayload = Generic<string>;

const data: StringPayload = {
  payload: 'данные',
};

Ограничение значений параметров

type Generic<T extends string> = {
  payload: T;
};

type NumberPayload = Generic<number>; // ОШИБКА (number != string)!
type StringPayload = Generic<string>;

Ограничение значений параметров через другие параметры

type Generic<K extends T, T extends string> = {
  [k in K]: T;
};

const data: Generic<'foo' | 'bar', string> = {
  foo: 'value1',
  bar: 'value2',
};

Ограничение параметра через самого себя

type Generic<T extends Required<T>> = T;

interface Dog {
  name: string;
  color?: string;
}

interface Cat {
  name: string;
  size: number;
}

type CarType = Generic<Dog>; // ОШИБКА (есть необязательное поле)!
type CatType = Generic<Cat>;

Условные типы

type ToArray<T> = T extends any[] ? T : T[];

type T1 = ToArray<string>; // string[]
type T2 = ToArray<string[]>; // string[]

Вывод типов

type ArrayItem<T> = T extends (infer U)[] ? U : never;

type Price = ArrayItem<number[]>; // number

Типы шаблонных строк (Template Literal Types)

Базовый синтаксис

type Name = 'Neo';
type Greet = `Hello ${Name}!`; // "Hello Neo!"

type Name = 'Neo' | 'Alice'; // Несколько значений
type Greet = `Hello ${Name}!`; // "Hello Neo!" | "Hello Alice!"

Тип string в шаблонных строках

type Greet = `Hello ${string}!`; // аналогичен регулярному выражению /^Hello .*!$/

const val1: Greet = 'Hello!'; // ОШИБКА!
const val2: Greet = 'Hello !';
const val3: Greet = 'Hello Neo!';

Тип 'true' | 'false'

type BoolAsString = `${boolean}`; // "false" | "true"

Сопоставление с шаблоном

type IsGreeting<T> = T extends `Hello ${string}!` ? true : false;

type Type1 = IsGreeting<'Hello world! Really?'>; // false
type Type2 = IsGreeting<'Hello world!'>; // true

Захват значения из шаблона

type GetAmount<T> = T extends `Amount: ${infer U}` ? U : never;

type Amount = GetAmount<'Amount: 10'>; // 10

Тип непустой строки

type NotEmptyString = `${any}${string}`;

const val1: NotEmptyString = ''; // ОШИБКА (пустая строка)!
const val2: NotEmptyString = 'text';

Тип строки ограниченной длинны

type AtLeastTwoChars = `${any}${string}${string}`; // Строка 2 или больше символов

const val1: AtLeastTwoChars = ''; // ОШИБКА!
const val2: AtLeastTwoChars = '1'; // ОШИБКА!
const val3: AtLeastTwoChars = '12';
const val4: AtLeastTwoChars = '123';

Брендирование типов

Основы

declare const kilometersBrand: unique symbol;

type Kilometers = number & {
  [kilometersBrand]: never;
};

function calculate(value: Kilometers) {
  return value * 2;
}

const kilometers = 1000 as Kilometers;
const meters = 3;

const val1 = calculate(meters); // ОШИБКА (функция принимает только километры)!
const val2 = calculate(kilometers);

Фабрика брендов

declare const kilometersBrand: unique symbol;
declare const metersBrand: unique symbol;

type Brand<TType, TBrand extends symbol> = TType & { [key in TBrand]: never };

type Meters = Brand<number, typeof metersBrand>;
type Kilometers = Brand<number, typeof kilometersBrand>;

function calculate(value: Kilometers) {
  return value * 2;
}

const meters = 10 as Meters;
const kilometers = 5 as Kilometers;

calculate(meters); // ОШИБКА (функция принимает только киллометры)!
calculate(kilometers);

Модификаторы полей

Добавить readonly и optional

type ReadonlyAndOptional<T> = { readonly [K in keyof T]?: T[K] }; // Короткая запись
type ReadonlyAndOptional<T> = { +readonly [K in keyof T]+?: T[K] }; // Полная запись

interface Car {
  name: string;
  price: number;
}

const car: ReadonlyAndOptional<Car> = {
  name: 'Toyota',
  // price - необязательное поле
};

car.name = 'BMW'; // ОШИБКА (значение readonly)!

Убрать readonly и optional

type NoReadonlyAndNoOptional<T> = { -readonly [K in keyof T]-?: T[K] }; // Полная запись

interface Car {
  readonly name?: string;
  readonly price?: number;
}

const obj: NoReadonlyAndNoOptional<Car> = {
  name: 'Toyota',
  price: 20000,
};

obj.name = 'Honda';

Изменение имени поля

// Преобразует все ключи объекта к нижнему регистру
type KeysToLowerCase<T> = { [K in keyof T & string as Lowercase<K>]: T[K] }; // as Lowercase<K> - меняет имя поля

interface Car {
  Name: string;
  Price: number;
}

type CarFixed = KeysToLowerCase<Car>;
// Вывод:
// type CarFixed = {
//     name: string;
//     price: number;
// }

Исключить поля по имени

type ExcludeKeys<T, P extends keyof T> = { [K in keyof T as K extends P ? never : K]: T[K] };

interface Props {
  name: string;
  size: number;
}

type PropsFixed = ExcludeKeys<Props, 'size'>; // {name: string}

Красивый вывод комбинированных типов

interface ShortData {
  id: number;
}

interface AdditionData {
  value: string;
}

type Data = ShortData & AdditionData; // Вывод: ShortData & AdditionData

Решение:

interface ShortData {
  id: number;
}

interface AdditionData {
  value: string;
}

type Prettify<T> = T extends object ? { [K in keyof T]: Prettify<T[K]> } : T;

type Data = Prettify<ShortData & AdditionData>;
// Вывод:
// type Data = {
//     id: number;
//     value: string;
// }

В статье использованы материалы доклада Андрея Тараненко:
Видео
Слайды