En muchas ocasiones te habrás encontrado con un error de tipo "string no es asignable a... Whatever" al asignar a una variable un valor compatible con el tipo esperado. Esto ocurre cuando TypeScript infiere un tipo más amplio que el que se espera para una variable, como por ejemplo, cuando asignamos un string a una variable que debería ser de tipo literal.
Para entenderlo mejor, vamos a verlo con un ejemplo.
Tenemos un tipo Severity definido como un string unión de los valores: "low", "medium" y "high".
type Severity = "low" | "medium" | "high"
Podríamos decir que Severity es un subtipo de string que, de todos los strings posibles, solo admite esos tres.
Por otra parte tenemos un objeto messages en el que las claves son un Severity y los valores son strings.
const messages: Record<Severity, string> = {
low: "😕 not good",
medium: "😖 ugly",
high: "😱 AAAAHHH!!!",
}
Queremos acceder a los valores de messages y para ello definimos una variable severity con el valor "low".
let severity = "low"
console.log(messages[severity])
// ^? Error: Element implicitly has an 'any' type because
// expression of type 'string' can't be used to index type
// 'Record<Severity, string>'.
Aunque el valor que hemos asignado a la variable severity es compatible con el tipo Severity, TypeScript nos muestra un error que viene a decirnos que las claves de messages deben ser de tipo Severity y no de tipo string.
Para entender por qué ocurre esto, necesitamos entender cómo TypeScript infiere los tipos de las variables a las que no asignamos un tipo explícitamente.
Si hacemos hover en nuestro editor o en el
En estos casos, TypeScript aplica lo que se conoce como "widening" y asigna a la variable el tipo más amplio que sea compatible con el valor que le hemos asignado inicialmente.
Ojo, digo "inicialmente" porque TypeScript espera que cambiemos el valor de una variable en runtime, pero no su tipo (de string a number, etc.). Si esta es nuestra intención, debemos especificarlo explícitamente.
No ocurre lo mismo si declaramos la variable con const.
const severity = "low"
console.log(messages[severity]) // OK
Puesto que el uso de const no permite la reasignación de la variable, TypeScript infiere el tipo como el tipo literal "low" y el error desaparece.
Otra forma de solucionar este problema, a efectos didácticos, es usar Object.freeze sobre el objeto.
const issue = Object.freeze({
id: '123',
severity: 'low',
})
Dado que Object.freeze devuelve un objeto de solo lectura, TypeScript infiere el tipo literal para sus propiedades.
Object.freeze tiene una característica que puede ser un inconveniente en algunos casos, y es que la inmutabilidad que proporciona es "shallow", es decir, solo afecta a las propiedades del objeto, pero no a sus propiedades anidadas, que pueden seguir siendo modificadas.
Si nos interesa marcar un objeto como readonly en profundidad, podemos aplicar as const a todo el objeto.
const issue = {
id: '123',
severity: 'low',
another: {
prop: 'value'
}
} as const
Y esto es todo en cuanto a inferencia de tipos y as const como técnica de "narrowing", pero tenemos otras formas de evitar que TypeScript infiera tipos más amplios de los que esperamos, como por ejemplo, especificando el tipo de las variables explícitamente, usando satisfies o aprovechando el "excess property checking", pero eso lo dejamos para otra píldora.
Si has llegado hasta aquí, ¡gracias! Y si además te ha resultado útil o simplemente te ha gustado, ayúdame a llegar a más gente compartiéndolo 😊

SOCIAL SHARE CARD GENERATOR