typeof в типах
В генератор анкет можно не вписывать поля руками. Достаточно положить в него уже заполненную анкету: он прочитает, какие в ней поля и что в них написано, и сделает по ней чистый бланк. Появится в образце новое поле — появится и в бланке.
В программах такой образец встречается постоянно. Объект с настройками уже написан, и функции, которая принимает настройки, нужен его тип. Его можно описать руками: type Settings = { theme: string; fontSize: number }. Но тогда каждое поле записано дважды, и новое поле придётся не забыть добавить и в объект, и в тип.
typeof берёт тип у значения
Оператор typeof в позиции типа берёт значение и возвращает его тип — тот, который 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Давайте разберём пример по строкам:
settings— обычный объект. TypeScript выводит для него тип{ theme: string; fontSize: number; }.type Settings = typeof settingsдаёт этому типу имя. Поля переписывать не пришлось.applySettingsпринимает объект той же формы и печатаетlight 16.- Последняя строка печатает
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". Если в программе есть и массив этих размеров, каждый размер записан дважды. Теперь тип можно получить из самого массива:
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:
as constсохраняет литералы, и уtypeof sizesтипreadonly ["S", "M", "L"].[number]— тот же доступ по ключу, что иUser["age"], только ключ здесь — любое число, то есть номер любого элемента.- Любой элемент массива — это
"S","M"или"L", поэтомуSize— объединение"L" | "M" | "S". - Вызовы
order("M")иorder(sizes[2])компилируются и печатаютMиL.
Скобки вокруг typeof sizes здесь для читателя: сначала берётся тип массива, затем тип его элемента. Размер не из списка tsc не пропустит — вызов order("XXL") даст ошибку:
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 написали перед типом:
interface User {
name: string;
age: number;
}
type Person = typeof User; // ✗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.