Irbisa · Шпаргалка

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

type и interface, any, unknown и never, сужение типов и type guards, структурная типизация, enum и литеральные union, дженерики и ограничения, keyof и typeof, mapped и conditional types, infer, .d.ts

Middle-разработчик · 18 вопросов

  1. 01

    В чём разница между type и interface в TypeScript и что вы используете по умолчанию?

    Оба описывают форму объекта и для большинства моделей и пропсов взаимозаменяемы.

    interface User {  id: string;  name: string;} // declaration merging: a second interface adds fields…
  2. 02

    В чём разница между any, unknown и never?

    any отключает проверку типов: ему можно присвоить что угодно и делать с ним что угодно.

    const loose: any = JSON.parse('{"name":"Ada"}');loose.name.toUpperCase(); // compilesloose.nmae.toUpperCase(); // compiles too, throws at runtime const data: unknown = JSON.parse('{"name":"Ada"}');// data.name; // error: 'data' is of type 'unknown'…
  3. 03

    Что происходит с типами TypeScript во время выполнения кода и почему нельзя написать value instanceof User, если User объявлен как интерфейс?

    Они исчезают: компилятор стирает все аннотации, интерфейсы и псевдонимы типов, и в исполняемом JavaScript от них не остаётся следа.

    interface User {  name: string;} class Account {  id: string;…
  4. 04

    Что такое сужение типов (narrowing) и по каким проверкам TypeScript сужает тип?

    Сужение означает, что TypeScript следует за вашими проверками во время выполнения по ходу кода: после if (typeof x === 'string') внутри ветки x имеет тип string, а после раннего return остаётся остаток объединения.

    function format(value: string | number | Date | null): string {  if (value === null) return '';                  // string | number | Date  if (typeof value === 'string') return value.trim();  if (value instanceof Date) return value.toISOString();  return value.toFixed(2);                        // only number is left}…
  5. 05

    Что такое объединения (union) и пересечения (intersection) типов и что получится из A & B, если оба объявляют одно свойство с разными типами?

    Объединение A | B описывает значение одного из типов, поэтому до сужения можно использовать только то, что есть у обоих.

    type Id = string | number; function show(id: Id): string {  // id.toUpperCase(); // error: does not exist on type 'number'  return typeof id === 'string' ? id.toUpperCase() : id.toFixed(0);}…
  6. 06

    TypeScript типизирован структурно: почему он принимает объект с лишним label в первом вызове и отклоняет во втором?

    interface Point {  x: number;  y: number;} function draw(p: Point) {  console.log(p.x, p.y);} const p = { x: 1, y: 2, label: 'A' };draw(p);                          // okdraw({ x: 1, y: 2, label: 'A' }); // error

    Структурная типизация означает, что значение подходит под тип, если у него есть нужные свойства, что бы ещё в нём ни было, поэтому p проходит.

  7. 07

    Что такое type guard (value is T) и функция-утверждение (asserts value is T) и чем рискованно их писать?

    Type guard возвращает value is T: когда он возвращает true, TypeScript сужает аргумент до T в этой ветке.

    interface User {  id: string;  name: string;} function isUser(value: unknown): value is User {…
  8. 08

    Почему многие команды избегают enum в TypeScript и что используют вместо него?

    enum относится к немногим возможностям TypeScript, которые генерируют код для рантайма, и ведёт себя не так, как остальная система типов: числовые enum отображают значения обратно в имена, строковые не принимают обычные литералы.

    enum Direction {  Up,  Down,}console.log(Direction[0]);          // 'Up': numeric enums map values back to namesconsole.log(Object.keys(Direction)); // ['0', '1', 'Up', 'Down']…
  9. 09

    Во что разворачивается каждый из этих псевдонимов типов и что здесь делают keyof, typeof и индексный доступ T[K]?

    const config = {  api: 'https://example.com',  retries: 3,  features: ['search', 'chat'],};const roles = ['admin', 'editor'] as const; type Config = typeof config;type ConfigKey = keyof Config;type Retries = Config['retries'];type Feature = Config['features'][number];type Role = (typeof roles)[number];type Dict = keyof { [key: string]: boolean };

    Config равен { api: string; retries: number; features: string[] }, ConfigKey равен 'api' | 'retries' | 'features', Retries равен number, Feature даёт просто string, Role равен 'admin' | 'editor', а Dict равен string | number.

  10. 10

    Как написать дженерик getProperty(obj, key), тип результата которого следует за ключом, и что означает K extends keyof T?

    Дайте функции два параметра типа, T для объекта и K extends keyof T для ключа, и верните T[K].

    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {  return obj[key];} const user = { name: 'Ada', age: 36, admin: false };…
  11. 11

    Что такое перегрузки функций в TypeScript и когда они лучше одной сигнатуры с параметром-объединением?

    Перегрузки представляют собой несколько сигнатур вызова над одной реализацией: вызывающий видит только перегрузки, а реализация должна обработать их все.

    interface User {  id: string;  name: string;}declare function fetchUser(id: string): Promise<User>;…
  12. 12

    Что такое файл .d.ts и как добавить типы для нетипизированного пакета или своё свойство в Window?

    Файл .d.ts содержит только объявления типов и никакого кода: он описывает JavaScript, который существует в другом месте.

    // src/types/globals.d.tsexport {}; // a module, so global additions go inside declare global declare global {  interface Window {    analytics?: { track(event: string, props?: Record<string, unknown>): void };…
  13. 13

    Что такое mapped type и как самому реализовать Pick, Omit и DeepPartial?

    Mapped type строит объектный тип, проходя циклом по объединению ключей: { [K in Keys]: T[K] }.

    type MyPick<T, K extends keyof T> = {  [P in K]: T[P];}; type MyOmit<T, K extends keyof T> = {  [P in keyof T as P extends K ? never : P]: T[P];…
  14. 14

    Что такое условные типы и как infer позволяет написать свой ReturnType или развернуть Promise?

    Условный тип `T extends U ?

    type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type UnwrapPromise<T> = T extends Promise<infer V> ? UnwrapPromise<V> : T; type ElementOf<T> = T extends readonly (infer E)[] ? E : never;…
  15. 15

    Что такое template literal types и как получить имена обработчиков вроде onClick из имени события вроде click?

    Template literal types строят строковые литеральные типы с синтаксисом обратных кавычек: `on${Capitalize<E>}` превращает 'click' в 'onClick', а объединение разворачивается во все комбинации.

    type EventName = 'click' | 'focus' | 'change'; type HandlerName<E extends string> = `on${Capitalize<E>}`;type Handlers = HandlerName<EventName>; // 'onClick' | 'onFocus' | 'onChange' type HandlerProps<T> = {…
  16. 16

    Во что разворачиваются эти четыре псевдонима и почему квадратные скобки вокруг обеих сторон extends меняют результат?

    type ToArray<T> = T extends unknown ? T[] : never;type ToArrayWhole<T> = [T] extends [unknown] ? T[] : never; type A = ToArray<string | number>;type B = ToArrayWhole<string | number>;type C = ToArray<never>;type D = ToArrayWhole<never>;

    A равен string[] | number[], B равен (string | number)[], C равен never, а D равен never[].

  17. 17

    Как сделать два строковых id, например UserId и OrderId, несовместимыми в TypeScript (branded types)?

    Пересеките базовый тип со свойством-меткой, которого нет ни у одного настоящего значения: type UserId = string & { readonly [brand]: 'UserId' }.

    declare const brand: unique symbol;type Brand<T, Name extends string> = T & { readonly [brand]: Name }; type UserId = Brand<string, 'UserId'>;type OrderId = Brand<string, 'OrderId'>;…
  18. 18

    Что такое ковариантность и контравариантность и почему TypeScript проверяет параметры методов мягче, чем свойства с типом функции?

    Ковариантность сохраняет направление подтипов: Dog[] можно использовать как Animal[].

    interface Animal { name: string }interface Dog extends Animal { bark(): void }interface Cat extends Animal { meow(): void } type PropHandler = { handle: (animal: Animal) => void }; // property: checked strictlytype MethodHandler = { handle(animal: Animal): void };   // method: bivariant…