Typescript Avanzado. Trucos y Tips

- 6 min read

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

El contenido de este sitio es y será siempre gratuito para todos. Ayudame a mantenerlo así convirtiendote en auspiciador.
Matias Hernández Logo

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)

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.

typescript
					
						
 
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.

typescript
					
						
 
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

typescript
					
						
 
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

typescript
					
						
 
type Data = {
    title: string;
    description: string;
    amount?: number;
}

type OnlyStringData = Pick<Data, "title" | "description'>

const info: OnlyStringData = { title: "Title", description: "description" }
 
					
				

Omit

typescript
					
						
 
type Data = {
    title: string;
    description: string;
    amount?: number;
}

type OnlyStringData = Omit<Data, "amount'>

const info: OnlyStringData = { title: "Title", description: "description" }
 
					
				

Record

sponsor

El contenido de este sitio es y será siempre gratuito para todos. Ayudame a mantenerlo así convirtiendote en auspiciador.
Matias Hernández Logo

Tu producto o servicio podría estar aquí

typescript
					
						
 
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

typescript
					
						
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

typescript
					
						

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

typescript
					
						
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.

typescript
					
						
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

typescript
					
						
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

typescript
					
						
class SomeClass {
    prop = 10
    prop2 = ['strings','string2']
}

type Instance1 = InstanceType<typeof SomeClass>


const instance: Instance1 = new SomeClass()

const instance2: Instance1 = SomeClasss // Error

					
				

ThisParameterType

😃 Gracias por leer!

Te pareció interesante? Encuentra más contenido similar uniendote al Newsletter o siguiendome en Twitter.

📖 Continúa leyendo