Si has llegado hasta aquí es por que ya sabes que usar Typescript en tus proyectos es una buena idea y ya pasaste el punto inicial de fricción de agregar tipos en tu código Javascript, ahora: ¿Qué más puedes hacer?
El uso de Tipos en tu código te permite expresar diferentes restricciones y requerimientos en la forma de tus datos. Muchos de estos casos de uso pueden ser considerados avanzados y también patrones que encontrarás constantemente, por ejemplo:
Es decir, tienes un tipo de dato con una restricción: Cuando un atributo está presente algún otro atributo no debe estarlo.
Este tipo de dato o patrón se conoce como “Discriminated Unions”.
sponsor
Tu producto o servicio podría estar aquí
Discriminated Unions
Hace un tiempo hice un hilo sobre esto mismo
¿Cómo expresas estas condiciones con @typescript?
La solución es una combinación de distintos tipos de datos y el uso de atributos opcionales y never
tal como en el siguiente ejemplo (Link al playground de typescript)
type WithUsername = {
username: string;
email?: never
}
type WithEmail = {
username?: never;
email: string;
}
type WithAvatar = {
emoji?: never;
avatar: string
}
type WithEmoji = {
emoji: string;
avatar?: never
}
type User = { id: number } & (WithUsername | WithEmail) & (WithAvatar | WithEmoji)
const userWithNameAndAvatar: User = { id: 1, username: 'username', avatar: 'avatar' }
const userWithEmailAndAvatar: User = { id: 2, email: 'email', avatar: 'avatar'}
const userWithNameAndEmoji: User = { id: 3, username: 'username', emoji: 'emoji' }
const userWithEmailAndEmoji: User = { id: 4, email: 'email', emoji: 'emoji'}
const wrongUser: User = { id:5, username: 'username', email: 'email', emoji: 'emoji'} // Error
const wrongUser2: User = { id:5, username: 'username', emoji: 'emoji', avatar: 'avatar' } // Error
Puedes ver que se crearon distintos tipos y luego un tipo que une todos los demás, la combinación de opcional y never
permite definir que un atributo este presente o dependiendo del otro atributo del conjunto.
Cuando creas tipos de datos complejos te encontrarás con ciertos patrones que fácilmente se pueden extraer como utilidades, y el equipo de Typescript lo sabe y ofrece variadas utilidades listas para usar, revisemos algunas
Partial:
Esta utilidad te permite construir un tipo de datos donde todas sus propiedades son opcionales, creando así un tipo que en efecto es un “sub-tipo” del tipo original.
type Data = {
title: string;
description: string;
amount: number;
}
type Context = Partial<Data>
const c1: Context = {}
const c2: Context = { title: 'Title' }
function updateContext(data: Context, attrToUpdate: Context) {
return {...data, ...attrToUpdate}
}
updateContext(c1, { amount: 10 }) // { amount: 10}
Required:
Al contrario de Partial, Required indica que todas las propiedades del tipo pasado como “argumento” son requeridas.
type Data = {
title?: string;
description?: string;
amount?: number;
}
type AllRequired = Required<Data>
const obj1: Data = {}
const obj2: AlLRequired = { title: 'Title'} // Error!
ReadOnly:
Te permite crear un tipo en donde todas las propiedades serán de solo lectura, es decir no se pueden modificar generando el mismo efecto que congelar un objeto con Object.freeze
pero en tiempo de “compilación” permitiendote encontrar erores antes de llegar al navegador
type Data = {
title: string;
description: string;
amount?: number;
}
const obj: ReadOnly<Data> = {
title: 'Titulo',
description: 'description'
}
const obj.title = 'Otro titulo'; // Error, title es de solo lectura
Pick
type Data = {
title: string;
description: string;
amount?: number;
}
type OnlyStringData = Pick<Data, "title" | "description'>
const info: OnlyStringData = { title: "Title", description: "description" }
Omit
type Data = {
title: string;
description: string;
amount?: number;
}
type OnlyStringData = Omit<Data, "amount'>
const info: OnlyStringData = { title: "Title", description: "description" }
Record
sponsor
Tu producto o servicio podría estar aquí
type Data = {
title: string;
description: string;
amount?: number;
}
type DataTypes = "type1" | "type2" | "type3" // union de strings
const sets: Record<DataTypes, Data> = {
type1: { title: 'Title1' , description: 'Description 1', amount: 10},
type3: { title: 'Title2' , description: 'Description 2', amount: 20},
type3: { title: 'Title3' , description: 'Description 3', amount: 30},
}
Exclude
interface Data1 {
title: string;
description: string;
subtitle: string;
}
interface Data2 {
date: string;
url: string;
}
interface Data3 {
image: string;
keywords: string[];
}
type Data = { id: string } & (Data1 | Data2 | Data3);
// Ahora necesitas un tipo de dato que solo posea
// las propiedades de Data1, Data2 y lo que se agregó en Data
// Es decir, excluir todo lo que proviene de Data3
type Excluded = Exclude<Data, Data3>
const obj: Excluded = {
title: '',
description: '',
date: '',
url: '',
id: ''
}
Extract
interface BaseUser {
email: string
image: string | null
username: string
}
interface Profile {
id: string
email: string
image: string | null
username: string
reviews: string[]
}
/*
* Imagina que ahora quieres encontrar los datos compartidos entre ambas interfaces.
* Para encontrar las "claves" de las propiedes puedes usar `keyof` lo que te dará una union de strings con todas las "keys" del tipo
* Y usar Extract para encontrar los valores compartidos
*/
type Shared = Extract<keyof BaseUser, keyof Profile> // 'email' | 'image' | 'username'
Parameters
function someFunction(arg1: string, arg2: number) {
}
/*
* lo mismo que
* type ParametersType = [ arg1: string, arg2: number]
*/
type ParametersType = Parameters<typeof someFunction>
function otherFunction({arg1, arg2 }: {arg1: string, arg2?: number}) {
}
/*
* lo mismo que
* type RestarametersType = [ {arg1: string, arg2: number | undefined}]
*/
type RestParametersType = Parameters<typeof otherFunction>
ConstructorParameters
En este caso esta utilidad te permite construir una tupla (o arreglo de tipos) en base a los tipos del constructor de una función.
class someClass {
constructor({ arg1, arg2}: { arg1: string, arg2?: number[]}) {
}
}
/**
* Esto genera un tipo con los tipos del constructor
* type ClassType = [{ arg1:string, arg2: number[] | undefined }]
*/
type ClassType = ConstructorParameters<typeof someClass>
ReturnType
Tal como su nombre lo indica, esta utilidad te permite obtener/extraer el tipo de datos retornados por una función
const someFunction = () => {
return {
a: '',
b: [{ attr: 10}]
}
}
/**
* type ReturnType = {
* a: string,
* b: Array<{ attr: number}>
* }
*/
type TheReturn = ReturnType<typeof someFunction>
InstanceType
Te permite crear un tipo consistente en el tipo de la instancia de un constructor
class SomeClass {
prop = 10
prop2 = ['strings','string2']
}
type Instance1 = InstanceType<typeof SomeClass>
const instance: Instance1 = new SomeClass()
const instance2: Instance1 = SomeClasss // Error
ThisParameterType
😃 Thanks for reading!
Did you like the content? Found more content like this by joining to the Newsletter or following me on Twitter