Menu

Typeof в TypeScript: защитники типов и оператор типа typeof

У typeof в TypeScript две задачи. В коде это оператор JavaScript, который во время выполнения возвращает "string", "number", "object" и так далее, а TypeScript сужает по нему типы. В типе это запрос типа, который копирует тип переменной, как в keyof typeof obj и ReturnType<typeof fn>.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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 для экземпляров классов; ни тот, ни другой не может проверить интерфейс или псевдоним типа, которых нет во время выполнения.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ