Menu

typeof en TypeScript: type guards y el operador de tipo typeof

typeof tiene dos trabajos en TypeScript. En el código es el operador de JavaScript que devuelve "string", "number", "object" y demás en tiempo de ejecución, y TypeScript estrecha con él. En un tipo es la consulta de tipo que copia el tipo de una variable, como en keyof typeof obj y ReturnType<typeof fn>.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

typeof significa dos cosas en TypeScript. En una expresión es el operador de JavaScript: se ejecuta, devuelve un string como "string" o "number", y TypeScript estrecha la variable cuando comparas ese string. En una posición de tipo es una consulta de tipo: typeof config da el tipo estático de la variable config.

La última línea muestra la diferencia: typeof custom dentro de un console.log es el operador de ejecución e imprime object, mientras que typeof defaults después de type Options = es la consulta en tiempo de compilación y nunca llega al JavaScript.

Los dos significados de un vistazo

typeof en tiempo de ejecución (JavaScript)typeof como consulta de tipo (TypeScript)
Dónde apareceCualquier expresión: if, return, console.log(...)Posiciones de tipo: después de :, en type X = ..., dentro de <...>
Ejemplotypeof x === "string"let y: typeof x;
Se evalúaCuando se ejecuta el códigoLo evalúa el compilador y después se borra
ResultadoUno de ocho stringsUn tipo
Sirve paraComprobar un valor y estrechar su tipoDerivar un tipo a partir de un valor

TypeScript los distingue por la posición, así que la misma palabra clave nunca significa las dos cosas a la vez.

Resultados de typeof en tiempo de ejecución

El operador devuelve uno de ocho strings. Ejecuta esto para ver qué dan los valores habituales:

ValorResultado de typeofNota
"hi", template strings"string"
42, 3.14, NaN, Infinity"number"NaN es un número; compruébalo con Number.isNaN
10n"bigint"
true, false"boolean"
undefined"undefined"
Symbol("id")"symbol"
funciones, funciones flecha, clases"function"una clase es una función constructora
null"object"un bug histórico de JavaScript
{}, [], new Date(), new Map()"object"los arrays son objetos: usa Array.isArray
new String("x"), new Number(1)"object"objetos envoltorio, evítalos

En JavaScript normal, typeof sobre una variable que nunca se declaró devuelve "undefined" en lugar de lanzar un error. En TypeScript ese código no compila: Cannot find name 'notDeclared'. (TS2304).

Estrechar con typeof

Una comparación con typeof en un if, un switch, un ternario o después de && estrecha la variable en cada rama. Es la forma estándar de manejar una unión de primitivos, y la primera comprobación a la que recurrir con unknown.

typeof x !== "string" estrecha en el otro sentido, a todo excepto string. Y el compilador conoce los ocho strings posibles, así que una errata en la comparación es un error de compilación y no una comprobación que siempre da false sin avisar:

El compilador muestra 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. En JavaScript normal la errata mandaría sin avisar todos los strings por el camino de number. Corrige la ortografía y se imprime text.

La trampa de typeof null

Como typeof null es "object", TypeScript estrecha typeof x === "object" a object | null, no a object. Añade una comprobación de null, o leer una propiedad será un error (TS18047 'x' is possibly 'null'.).

typeof no distingue arrays, fechas, maps y objetos normales: todos son "object". Usa Array.isArray(x) para arrays y x instanceof Date para instancias de clases. Ni typeof ni ninguna otra cosa puede comprobar en tiempo de ejecución si un valor cumple una interfaz, porque las interfaces se borran; para eso, escribe una función type guard que compruebe las propiedades.

typeof en posiciones de tipo

En un tipo, typeof someVariable copia el tipo que TypeScript infirió (o que declaraste) para esa variable. Te ahorra escribir a mano un tipo para un valor que ya existe y mantiene los dos sincronizados.

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)

El resultado depende de cómo se declaró la variable: un let o una propiedad de objeto se ensanchan (string, number), un primitivo const conserva su tipo literal. Añade as const a un objeto o array y typeof da tipos literales readonly en todos los niveles. typeof también acepta acceso a propiedades, typeof theme.spacing es number, pero no expresiones arbitrarias: type T = typeof getTheme(); es un error de sintaxis (';' expected., TS1005). Para el tipo que devuelve una llamada, usa ReturnType<typeof getTheme>.

keyof typeof: las claves de un objeto como tipo

keyof typeof obj es la combinación más habitual. typeof convierte el objeto en un tipo, keyof toma sus claves, y obtienes una unión de los nombres de propiedad que sigue siendo correcta cuando el objeto cambia.

El mismo truco sobre un array constante da una unión de sus valores: con const roles = ["admin", "editor"] as const, el tipo (typeof roles)[number] es "admin" | "editor". Sin as const sería solo string. La página de keyof trata keyof por separado.

ReturnType<typeof fn> y typeof sobre clases

Utility types como ReturnType y Parameters reciben un tipo de función. El nombre de una función es un valor, así que pásalo antes por typeof:

Escribir ReturnType<createUser> falla con 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), que es el compilador pidiendo exactamente esto.

Las clases son el único caso en el que un nombre es a la vez un valor y un tipo. User como tipo significa una instancia de la clase; typeof User significa la clase en sí, el constructor, incluidos sus miembros estáticos:

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 frente a instanceof y a un type guard

ComprobaciónFunciona conEjemplo
typeofprimitivos, funciones, «¿es un objeto?»typeof x === "number"
Array.isArrayarraysArray.isArray(x)
instanceofinstancias de clases (Date, Error, tus clases)x instanceof Date
inuniones de objetos, por propiedad"email" in x
Función type guardinterfaces, alias de tipo, cualquier otra cosaisUser(x) con x is User

Elige la herramienta más específica que encaje. Para instancias de clases consulta instanceof; para formas descritas por una interfaz, una función type guard es la única comprobación posible en tiempo de ejecución.

Preguntas frecuentes

¿Qué hace typeof en TypeScript?

Dos cosas distintas según dónde aparezca. En una expresión, typeof x es el operador de JavaScript: se ejecuta y devuelve un string como "string" u "object", y TypeScript estrecha x cuando comparas ese string. En una anotación de tipo, typeof x es una consulta de tipo: la evalúa el compilador, da el tipo estático de la variable x y desaparece del JavaScript generado.

¿Qué es keyof typeof en TypeScript?

keyof typeof obj da la unión de los nombres de propiedad de un objeto como tipos literales de string. typeof obj convierte el valor objeto en su tipo, y keyof toma las claves de ese tipo. Para const colors = { red: "#f00", blue: "#00f" }, keyof typeof colors es "red" | "blue".

¿Por qué typeof null devuelve "object"?

Es un bug de la primera versión de JavaScript que ya no se puede corregir sin romper la web. TypeScript lo modela: tras typeof x === "object", x se estrecha a object | null, así que también debes comprobar x !== null antes de leer propiedades.

¿Cómo compruebo si un valor es un array en TypeScript?

Usa Array.isArray(value), que estrecha a un tipo array. typeof no puede hacerlo: typeof [] es "object", igual que para los objetos normales y para null.

¿Qué diferencia hay entre typeof e instanceof en TypeScript?

typeof comprueba la categoría primitiva de un valor ("string", "number", "function", "object"...). instanceof comprueba si un objeto lo creó una clase o un constructor concreto, como Date o tu propia class User. Usa typeof para primitivos e instanceof para instancias de clases; ninguno puede comprobar una interfaz o un alias de tipo, que no existen en tiempo de ejecución.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR