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 aparece | Cualquier expresión: if, return, console.log(...) | Posiciones de tipo: después de :, en type X = ..., dentro de <...> |
| Ejemplo | typeof x === "string" | let y: typeof x; |
| Se evalúa | Cuando se ejecuta el código | Lo evalúa el compilador y después se borra |
| Resultado | Uno de ocho strings | Un tipo |
| Sirve para | Comprobar un valor y estrechar su tipo | Derivar 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:
| Valor | Resultado de typeof | Nota |
|---|---|---|
"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ón | Funciona con | Ejemplo |
|---|---|---|
typeof | primitivos, funciones, «¿es un objeto?» | typeof x === "number" |
Array.isArray | arrays | Array.isArray(x) |
instanceof | instancias de clases (Date, Error, tus clases) | x instanceof Date |
in | uniones de objetos, por propiedad | "email" in x |
| Función type guard | interfaces, alias de tipo, cualquier otra cosa | isUser(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.