typeof в TypeScript означает две вещи. В выражении это оператор JavaScript: он выполняется, возвращает строку вроде "string" или "number", и TypeScript сужает переменную, когда вы сравниваете эту строку. В позиции типа это запрос типа: typeof config даёт статический тип переменной config.
Последняя строка показывает разницу: typeof custom в console.log это оператор времени выполнения, и он печатает object, а typeof defaults после type Options = это запрос времени компиляции, который никогда не попадает в JavaScript.
Два значения вкратце
typeof времени выполнения (JavaScript) | Запрос типа typeof (TypeScript) | |
|---|---|---|
| Где встречается | В любом выражении: if, return, console.log(...) | В позициях типа: после :, в type X = ..., внутри <...> |
| Пример | typeof x === "string" | let y: typeof x; |
| Когда вычисляется | Во время выполнения кода | Компилятором, затем стирается |
| Результат | Одна из восьми строк | Тип |
| Для чего | Проверить значение и сузить его тип | Получить тип из значения |
TypeScript различает их по позиции, поэтому одно и то же ключевое слово никогда не означает обе вещи сразу.
Результаты typeof во время выполнения
Оператор времени выполнения возвращает одну из восьми строк. Запустите пример, чтобы увидеть, что дают распространённые значения:
| Значение | Результат typeof | Примечание |
|---|---|---|
"hi", шаблонные строки | "string" | |
42, 3.14, NaN, Infinity | "number" | NaN это число; проверяйте его через Number.isNaN |
10n | "bigint" | |
true, false | "boolean" | |
undefined | "undefined" | |
Symbol("id") | "symbol" | |
| функции, стрелочные функции, классы | "function" | класс это функция-конструктор |
null | "object" | историческая ошибка JavaScript |
{}, [], new Date(), new Map() | "object" | массивы это объекты: используйте Array.isArray |
new String("x"), new Number(1) | "object" | объекты-обёртки, избегайте их |
В обычном JavaScript typeof для переменной, которая никогда не объявлялась, возвращает "undefined", а не бросает исключение. В TypeScript такой код не компилируется: Cannot find name 'notDeclared'. (TS2304).
Сужение через typeof
Сравнение typeof в if, switch, тернарном операторе или после && сужает переменную в каждой ветке. Это стандартный способ обработать объединение примитивов и первая проверка, к которой стоит обратиться для unknown.
typeof x !== "string" сужает в обратную сторону, до всего, кроме string. А поскольку компилятор знает восемь возможных строк, опечатка в сравнении даёт ошибку компиляции, а не проверку, которая молча всегда ложна:
Компилятор выводит index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. В обычном JavaScript опечатка молча отправила бы каждую строку по пути number. Исправьте написание, и код напечатает text.
Ловушка typeof null
Поскольку typeof null равно "object", TypeScript сужает typeof x === "object" до object | null, а не до object. Добавьте проверку на null, иначе чтение свойства даст ошибку (TS18047 'x' is possibly 'null'.).
typeof не отличает массивы, даты, Map и обычные объекты: все они "object". Используйте Array.isArray(x) для массивов и x instanceof Date для экземпляров классов. Ни typeof, ни что-либо ещё не может проверить во время выполнения, соответствует ли значение интерфейсу, потому что интерфейсы стираются; для этого напишите функцию-защитник типа, которая проверяет свойства.
typeof в позициях типа
В типе typeof someVariable копирует тип, который TypeScript вывел (или вы объявили) для этой переменной. Это избавляет от ручного описания типа для уже существующего значения и держит их в согласии.
const theme = {
primary: "#3178c6",
spacing: 8,
dark: false,
};
type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }
function withSpacing(t: Theme, factor: number): Theme {
return { ...t, spacing: t.spacing * factor };
}
console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }
let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed; // "Ada" (a const keeps its literal type)
Результат зависит от того, как объявлена переменная: let или свойство объекта расширяются (string, number), примитив в const сохраняет литеральный тип. Добавьте as const к объекту или массиву, и typeof даст readonly литеральные типы на всю глубину. typeof принимает и обращение к свойству, typeof theme.spacing это number, но не произвольные выражения: type T = typeof getTheme(); это синтаксическая ошибка (';' expected., TS1005). Для типа, который возвращает вызов, используйте ReturnType<typeof getTheme>.
keyof typeof: ключи объекта как тип
keyof typeof obj самая частая комбинация. typeof превращает объект в тип, keyof берёт его ключи, и вы получаете объединение имён свойств, которое остаётся верным при изменении объекта.
Тот же приём с константным массивом даёт объединение его значений: при const roles = ["admin", "editor"] as const тип (typeof roles)[number] равен "admin" | "editor". Без as const это был бы просто string. Сам keyof описан на странице keyof.
ReturnType<typeof fn> и typeof для классов
Служебные типы вроде ReturnType и Parameters принимают тип функции. Имя функции это значение, поэтому сначала пропустите его через typeof:
Запись ReturnType<createUser> падает с ошибкой 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), и компилятор просит именно этого.
Классы это единственный случай, когда имя одновременно значение и тип. User как тип означает экземпляр класса; typeof User означает сам класс, конструктор, включая его статические члены:
class Point {
static origin = new Point(0, 0);
constructor(public x: number, public y: number) {}
}
const p: Point = new Point(1, 2); // an instance
const Ctor: typeof Point = Point; // the class (constructor + statics)
const q = new Ctor(3, 4); // q: Point
type Instance = InstanceType<typeof Point>; // Point
typeof, instanceof или защитник типа
| Проверка | Работает для | Пример |
|---|---|---|
typeof | примитивов, функций, вопроса «объект ли это» | typeof x === "number" |
Array.isArray | массивов | Array.isArray(x) |
instanceof | экземпляров классов (Date, Error, ваши классы) | x instanceof Date |
in | объединений объектов, по свойству | "email" in x |
| Функция-защитник типа | интерфейсов, псевдонимов типов и всего остального | isUser(x) с x is User |
Выбирайте самый узкий подходящий инструмент. Для экземпляров классов см. instanceof; для форм, описанных интерфейсом, функция-защитник типа единственная доступная проверка во время выполнения.
Часто задаваемые вопросы
Что делает typeof в TypeScript?
Две разные вещи в зависимости от места. В выражении typeof x это оператор JavaScript: он выполняется и возвращает строку вроде "string" или "object", а TypeScript сужает x, когда вы сравниваете эту строку. В аннотации типа typeof x это запрос типа: его вычисляет компилятор, он даёт статический тип переменной x и исчезает из сгенерированного JavaScript.
Что такое keyof typeof в TypeScript?
keyof typeof obj даёт объединение имён свойств объекта в виде строковых литеральных типов. typeof obj превращает значение-объект в его тип, а keyof берёт ключи этого типа. Для const colors = { red: "#f00", blue: "#00f" } выражение keyof typeof colors равно "red" | "blue".
Почему typeof null возвращает "object"?
Это ошибка из первой версии JavaScript, которую уже нельзя исправить, не сломав веб. TypeScript это учитывает: после typeof x === "object" переменная x сужается до object | null, поэтому перед чтением свойств нужно проверить ещё и x !== null.
Как проверить, что значение является массивом, в TypeScript?
Используйте Array.isArray(value), который сужает до типа массива. typeof этого не умеет: typeof [] равно "object", как и для обычных объектов и null.
Чем typeof отличается от instanceof в TypeScript?
typeof проверяет примитивную категорию значения ("string", "number", "function", "object"...). instanceof проверяет, создан ли объект определённым классом или конструктором, например Date или вашим class User. Используйте typeof для примитивов, а instanceof для экземпляров классов; ни тот, ни другой не может проверить интерфейс или псевдоним типа, которых нет во время выполнения.