Использование возможностей Angular. Часть 2

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Utility Types или почему я не люблю enum

Добрый день всем читателям и писателям. Меня опять зовут Юрик и я опять сочиняю про Angular. В этой части разговора будет больше про TS, но расскажу зачем вообще использовать utility types.

Итак, на собеседованиях часто спрашивают про utility types, коронный добивающий вопрос по ним связан с infer. О нем расскажу в конце статьи. Только вот интервьюеры в ответ что-то не хотят рассказывать, а как собственно они применяют эти самые utility types, какие задачи или проблемы решают.

Сначала посмотрим, а что нам говорит по этому поводу документация.

https://www.typescriptlang.org/docs/handbook/utility-types.html

Из документации следует, что в TS существую типы, которые проводят модификации других типов. Рассмотрим простой Pick<Type, Keys>

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

Кто-то сразу догадался, но уверен большинство новичков не сразу или не догадались. Наш Pick<Type, Keys> забирает Keys из типа (интерфейса) Type, результатом которого будет новый тип. При этом, эти типы будут связаны. Изменение интерфейса Todo необратимо изменит константу todo еще до рантайма. Ошибка в определении типов вызовет ошибку сборки бандла. И если в enum изменение запросто может пройти сборку и ошибка в рантайме вызовет красную консоль, то тут нет. В принципе, нет ничего плохого в использовании enum и дальше, но utility types намного технологичнее что-ли. Тем более, что использовать enum надо тоже с умом. Кто может объяснить разницу использования?

export enum Todo {
  //someting
}

export const enum Todo2 {
  //something
}

С Pick<Type, Keys> понятно. В документации представлены еще куча подобных types. Но эти utility types не являются частью языка, как enum, например. Они написаны на TS. Давайте разберем как это работает на примере нашего Pick<Type, Keys> В коде это выглядит следующим образом:

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Вот тут вот уже намного больше людей скажут:

да-да, именно так и скажут
да-да, именно так и скажут

Вот тут вот уже все написано именно на TS. И таких типов мы можем написать самостоятельно сколько угодно ровно под наши задачи. Например, трансформация JSON camelCase в kebab-case CSS. Дочитайте до конца и это там будет. А как работает, спросите вы? Да очень просто, отвечу я. Разберем с самого начала что, откуда и как интерпретируется интерпретатором и как это потом используется в JS.

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
  1. Определяется тип Pick с двумя дженериками <T, K>

  2. С типом T все понятно - это или тип или интерфейс, в вот с K extends keyof T посмотрим внимательнее. К соответствует ключам T, вот как это будет объяснено, т.е. keyof T имеет тип string и K должен быть строкой и соответствовать ключам типа T. Если мы в K поместим строку не соответствующую ключам T - будет ошибка компиляции. А мы еще до создания самого типа не дошли.

  3. Создаем тип. Выражение [P in K]: определяет keys объекта, который будет создан по типу. Поэтому определяется еще один дженерик P и он хранит тип ключа. Получаем что-то вроде typeof P === 'key name' , а т.к. P in K , то оператор in обходит циклом все значения K. Итак, мы получаем тип с ключами, представленными в К, и которые являются ключами T

  4. Каждому ключу назначаем его value. Выражение T[P]; прямо нам и говорит, берем value из объекта T по ключу P

Тут я намеренно писал "из объекта" потому что типы не являются объектами, но по ним строится объект. Именно объект проверяется на соответствие типу и именно объект потом уходит в JS. Как мы знаем вся писанина по типам TS останется за бортом конечного бандла JS.

Штош, как он работает мы определились. А как насчет того как его применять? Поехали дальше.

Конкретно Pick<Type, Keys> можно применять для определения strict partial model, т.е. нам нужна конкретная часть определенной модели данных например. Если не строгая модель, то мы имеем тип Patrial<T>

Где еще? Давайте сделаем обещанный camelCase to kebab-case. Для чего он нужен? Когда-то делал очень продвинутый  WYSIWYG-редактор и маппер перевода данных из JSON в SafeStyle был типизирован и обходил ровно то, что относилось к типу, а не то, что прилетело.

Что сначала? Сначала типизируем модель JSON.

type TextStylePropertyType =
  | 'fontWeight'
  | 'color'
  | 'fontFamily'
  | 'fontSize'
  | 'textDecoration'
  | 'letterSpacing'
  | 'lineHeight'
  | 'textAlign'
  | 'fontStyle'
  | 'borderRadius';

export type UnionTypeToValue<T extends string> = {
  [K in T]: any;
};

const textStyle: UnionTypeToValue<TextStylePropertyType> = {
  fontWeight: 'none',
  color: '#ffffff',
  fontFamily: 'Muli',
  fontSize: 16,
  textDecoration: 'none',
  letterSpacing: 'normal',
  lineHeight: 'normal',
  textAlign: 'left',
  fontStyle: 'none',
  borderRadius: 0,
};

Тут все понятно? Имеем union type TextStylePropertyType, на основании которого создаем объект стилизации текста с дефолтными значениями. Теперь делаем тип для kebab.

type ToKebab<T extends string, R extends string = ''> 
= T extends `${infer First}${infer Rest}`
  ? Uppercase<First> extends First
    ? ToKebab<Rest, `${R}-${Lowercase<First>}`>
    : ToKebab<Rest, `${R}${First}`>
  : R;

и вот тут вылазит наш infer на сцену и начинает мутить воду. Что нам говорит документация. Документация нам говорит, что infer - это type inference, т.е. вывод типа. Мы можем проверить соответствие типа и задать логику. В типах. Логику.

Источник: https://habr.com/ru/articles/803847/


Интересные статьи

Интересные статьи

Как всем известно, в настоящее время одним из популярных микроконтроллеров у любителей электроники, являются микроконтроллеры семейства STM32. Оно и не удивительно: богатая переферия, обилие различных...
Как, загружая гемы Ruby из кеша в GitHub Actions, ускорить запуск сборки проекта в этой системе непрерывной интеграции (CI)? Если суметь подготовить к работе все зависимости Ruby on Rails...
Предлагается сделать домашнего робота, отличительными чертами которого будут: — относительная простота создания (как по времени, так и трудозатратам), — недорогие комплектующие, — д...
Разбираясь с современными методами организации multicast VPN я заметил, что в сети не так много материала, описывающего принципы и детали работы технологий. На сайте вендора представлена ...
Барт Хендрикс, понедельник, 2 ноября 2020 г.Первоисточник:[Часть 1 была опубликована на прошлой неделе] Читать далее