Inferencia, anotaciones y contextual typing en TypeScript | Nicolás Garzón
TypeScript no necesita una anotación en cada valor. Utiliza información del código para inferir tipos y utiliza el contexto para comprobar expresiones.
TypeScript
Copiar const quantity = 3 ;
const name = "Keyboard" ; TypeScript
Copiar let quantity: number = 3 ; La anotación declara un contrato para el binding.
TypeScript
Copiar let quantity: number = "3" ; produce un error; TypeScript no transforma el string en número.
TypeScript
Copiar let total = 0 ;
total = 100 ; total se infiere como number.
TypeScript
Copiar total = "100" ; no es compatible con el tipo inferido.
TypeScript
Copiar const name: string = "Nicolás" ;
const active: boolean = true ;
const count: number = 0 ; TypeScript
Copiar const name = "Nicolás" ;
const active = true ;
const count = 0 ; La anotación no aporta información nueva.
TypeScript
Copiar let status: "idle" | "loading" | "success" = "idle" ; Sin anotación, un let inicializado con un string suele ampliarse a string porque puede recibir otros strings.
Aquí el tipo limita el estado a posibilidades válidas.
TypeScript
Copiar function calculateSubtotal (
price: number ,
quantity: number ,
) {
return price * quantity;
} Los parámetros de una declaración normal no tienen un valor inicial desde el cual inferir un contrato general. Bajo noImplicitAny, debes tiparlos o proporcionar un contexto.
TypeScript
Copiar function calculateSubtotal (
price: number ,
quantity: number ,
) {
return price * quantity;
} El retorno se infiere como number.
TypeScript
Copiar export function createProduct ( input: ProductInput) : Product {
return {
id: createId ( ) ,
... input,
} ;
} Una anotación de retorno puede:
Proteger una API pública.
Evitar que un detalle accidental se vuelva parte del contrato.
Detectar una rama sin retorno compatible.
Mejorar declarations y documentación.
En helpers locales simples, la inferencia suele ser suficiente.
TypeScript
Copiar export function getUser ( ) {
return {
id: "user-1" ,
internalToken: "secret" ,
} ;
} El tipo público incluye internalToken.
TypeScript
Copiar type PublicUser = {
id: string ;
} ;
export function getUser ( ) : PublicUser {
return {
id: "user-1" ,
} ;
} El contexto puede tipar una función sin anotaciones internas.
TypeScript
Copiar const names = [ "Nicolás" , "Ana" ] ;
names. map ( ( name) => name. toUpperCase ( ) ) ; map conoce el tipo de elementos y utiliza su firma para inferir que name es string.
TypeScript
Copiar type Formatter = ( value: number ) => string ;
const formatCurrency: Formatter = ( value) => {
return ` $ ${ value. toFixed ( 2 ) } ` ;
} ; value se tipa por contexto y el retorno se comprueba contra string.
TypeScript
Copiar button. addEventListener ( "click" , ( event) => {
event. currentTarget;
} ) ; La firma de addEventListener proporciona el tipo del evento.
Extraer la callback sin contrato puede perder ese contexto:
TypeScript
Copiar const handleClick = ( event) => { } ; Bajo reglas estrictas, event queda implícitamente any.
TypeScript
Copiar const handleClick: EventListener = ( event) => { } ; TypeScript
Copiar const values = [ 1 , 2 , null ] ; TypeScript busca un tipo capaz de representar todos los elementos, por ejemplo (number | null)[] bajo null checking estricto.
Si las posibilidades no representan la intención, declara el contrato.
TypeScript
Copiar function pair < T > ( first: T , second: T ) {
return [ first, second] as const ;
} El checker intenta encontrar un tipo compatible con ambos argumentos. En generics, la inferencia puede producir un tipo más amplio del esperado o fallar si las relaciones son incompatibles.
Conceptualmente, TypeScript utiliza información:
Desde los valores hacia el tipo.
Desde el contexto esperado hacia la expresión.
TypeScript
Copiar const handler : ( value: string ) => void = ( value) => {
console . log ( value. length) ;
} ; El contexto viaja hacia la callback.
TypeScript
Copiar const product: Product = value; El valor debe ser compatible.
TypeScript
Copiar const product = value as Product; Le indica al checker que acepte una interpretación permitida, aunque no exista una demostración completa.
Prefiere anotaciones, narrowing o validación antes que assertions.
TypeScript
Copiar let product: Product; La anotación define qué podrá asignarse después.
El análisis de asignación definida intenta impedir leerla antes de recibir un valor.
TypeScript
Copiar class ProductService {
private repository: ProductRepository;
constructor ( repository: ProductRepository) {
this . repository = repository;
}
} La anotación comunica el contrato de una propiedad cuyo valor llega más tarde.
TypeScript no ejecuta funciones para descubrir el resultado real ni consulta una API externa.
TypeScript
Copiar const value = JSON . parse ( text) ; Históricamente JSON.parse retorna any, por lo que no existe una inferencia segura del contenido. Una capa de parsing debe devolver un tipo comprobado.
TypeScript
Copiar type Configuration = {
apiUrl: string ;
retries: number ;
} ;
export const configuration: Configuration = {
apiUrl: process. env. API_URL ?? "" ,
retries: 3 ,
} ; La anotación comprueba que el objeto satisface la API, aunque más adelante satisfies permitirá conservar una inferencia más específica en ciertos casos.
Anotar cada valor local.
Pensar que una anotación convierte datos.
Omitir tipos de parámetros sin contexto.
Dejar que una implementación accidental defina una API pública.
Extraer callbacks y perder contextual typing.
Usar assertion cuando una anotación detectaría el problema.
Esperar inferencia segura desde JSON o red.
Forzar tipos explícitos que contradicen el valor real.
La inferencia reduce repetición.
Las anotaciones declaran intención y límites.
Los parámetros necesitan tipo o contexto.
Los retornos pueden inferirse.
Las APIs públicas se benefician de contratos explícitos.
Contextual typing fluye desde la API hacia callbacks y expresiones.
Una assertion pide confianza; no demuestra compatibilidad runtime.
Los datos externos necesitan parsing.
¿Por qué name no necesita anotación dentro de este callback?
TypeScript
Copiar [ "Ana" , "Nicolás" ] . map ( ( name) => name. length) ; Respuesta Porque la firma de map conoce que el array contiene strings y proporciona ese tipo como contexto para el parámetro de la callback.
Literal types, widening, as const y satisfies explica por qué un valor específico a veces se amplía y cómo conservar precisión sin mentir al checker.