Перейти к содержимому
Теория · Операторы типов и утилиты · урок 3 из 8

typeof в типах

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

В программах такой образец встречается постоянно. Объект с настройками уже написан, и функции, которая принимает настройки, нужен его тип. Его можно описать руками: type Settings = { theme: string; fontSize: number }. Но тогда каждое поле записано дважды, и новое поле придётся не забыть добавить и в объект, и в тип.

typeof берёт тип у значения

Оператор typeof в позиции типа берёт значение и возвращает его тип — тот, который TypeScript знает об этом значении:

TypeScript
const settings = { theme: "dark", fontSize: 14 };

type Settings = typeof settings;

function applySettings(s: Settings) {
    console.log(s.theme, s.fontSize);
}

applySettings({ theme: "light", fontSize: 16 }); // light 16
console.log(typeof settings); // object

Давайте разберём пример по строкам:

  1. settings — обычный объект. TypeScript выводит для него тип { theme: string; fontSize: number; }.
  2. type Settings = typeof settings даёт этому типу имя. Поля переписывать не пришлось.
  3. applySettings принимает объект той же формы и печатает light 16.
  4. Последняя строка печатает object. Здесь typeof — другой оператор, о нём рассказывает следующий раздел.

typeof берёт выведенный тип, а не сами значения. У theme тип string, а не "dark", потому что свойства объекта TypeScript расширяет так же, как переменные let.

Здесь генератор анкет отличается от typeof. Генератор читает настоящую бумагу, а typeof смотрит только на тип, который TypeScript вывел при компиляции. Что окажется в объекте во время работы программы, он не знает.

Два разных typeof

В примере typeof встречается дважды, и это два разных оператора. Какой из них перед вами, видно по месту, где он стоит.

typeof в коде

Это оператор JavaScript. Он работает при запуске и возвращает строку с названием вида значения: "object", "number", "string". Строку можно напечатать, сравнить или сохранить в переменную. Именно этот оператор работает в сужении: typeof v === "number".

typeof в типе

Это оператор TypeScript. Он стоит там, где ждут тип: после type Settings = или после двоеточия в аннотации. Он работает при компиляции, возвращает тип и вместе с остальными типами исчезает из JavaScript.

У обоих операторов справа стоит значение: имя переменной, функции или объекта.

Список значений как объединение

Особенно полезен typeof в типах вместе с as const. Вспомним тип Size: его варианты мы перечисляли руками, type Size = "S" | "M" | "L". Если в программе есть и массив этих размеров, каждый размер записан дважды. Теперь тип можно получить из самого массива:

TypeScript
const sizes = ["S", "M", "L"] as const;

type Size = (typeof sizes)[number];

function order(size: Size) {
    console.log(size);
}

order("M"); // M
order(sizes[2]); // L

Разберём, как получился тип Size:

  1. as const сохраняет литералы, и у typeof sizes тип readonly ["S", "M", "L"].
  2. [number] — тот же доступ по ключу, что и User["age"], только ключ здесь — любое число, то есть номер любого элемента.
  3. Любой элемент массива — это "S", "M" или "L", поэтому Size — объединение "L" | "M" | "S".
  4. Вызовы order("M") и order(sizes[2]) компилируются и печатают M и L.

Скобки вокруг typeof sizes здесь для читателя: сначала берётся тип массива, затем тип его элемента. Размер не из списка tsc не пропустит — вызов order("XXL") даст ошибку:

TEXT
error TS2345: Argument of type '"XXL"' is not assignable to parameter of type '"L" | "M" | "S"'.

Список размеров записан один раз. Добавьте в массив "XL", и тип Size сразу его примет.

Без as const приём не работает. У массива ["S", "M", "L"] тип string[], поэтому (typeof sizes)[number] даёт обычный string. К такому параметру подойдёт любая строка, и tsc не скажет ни слова.

Как получить объединение из массива значений, часто спрашивают на собеседовании. Ответ — as const у массива и (typeof sizes)[number].

Частая ошибка

typeof написали перед типом:

TypeScript
interface User {
    name: string;
    age: number;
}

type Person = typeof User; // ✗
TEXT
error TS2693: 'User' only refers to a type, but is being used as a value here.

tsc сообщает: имя User обозначает только тип, а здесь стоит на месте значения. typeof ждёт справа значение, а у интерфейса значения нет: он существует только для компилятора. Брать тип у типа не нужно — пишите type Person = User или используйте User напрямую.

Главное

  • typeof settings в позиции типа даёт тип значения settings, выведенный TypeScript.
  • typeof в коде — оператор JavaScript: при запуске он возвращает строку вроде "object". typeof в типе исчезает при компиляции.
  • (typeof sizes)[number] вместе с as const превращает список значений в объединение "L" | "M" | "S".
  • Справа от typeof всегда значение: typeof User для интерфейса — ошибка TS2693.