Type Guards & Type Assertions
Tags: spanish, typescript
Published: 2024-04-30 07:52:29

En este artículo vamos a ver cómo podemos filtrar correctamente los elementos de un array quedándonos con los tipos que tienen asociados los elementos que lo conforman. Para ello vamos a suponer que partimos de un tipo de datos `ResponseData` que viene a simular la respuesta que nos ha proporcionado un API y dicho dato solamente tiene un atributo denominado `data` que es de tipo `string`:

```tsx
type ResponseData = {
  data: string
}
```

Ahora vamos a suponer que definimos un array de elementos que concuerdan con este tipado pero sin establecer que el array está formado por elementos de este tipo pudiendo de esta manera afirmar que está incluyendo objetos del tipo `ResponseData`:

```tsx
const items = [{ data: 'Banana' }, { data: 'Dog' }]
```

Para hacer las cosas más interesantes vamos a añadir algún elemento más al array que estamos asignando a `items` que sea `undefined` dejándolo definitivamente tal y como vemos a continuación:

```tsx
const items = [{ data: 'Banana' }, undefined, { data: 'Dog' }, undefined]
```

Ahora que tenemos nuestro conjunto de datos de partida lo que vamos a hacer es añadir la anotación del tipo que estará asociado con `items` que sabemos que será un array de que estará formado por objetos del tipo `ResponseData` o `undefined`, es decir, que lo definimos como sigue:

```tsx
const items: (ResponseData | undefined)[] = [
  { data: 'Banana' },
  undefined,
  { data: 'Dog' },
  undefined
]
```

Vamos ahora a definir una función que se encargue de filtrarnos todos los elementos del array que no son `undefined` por lo que escribiremos algo como lo siguiente:

```tsx
const payloads = items.filter(items => item !== undefined)
```

Y ya para terminar vamos a escribir en la consola del sistema todos aquellos elementos que forman parte del array `payloads` que, de forma intuitiva, podemos ver que se tratará de todos aquellos que no son `undefined` lo que nos dejará un código final que será similar al que se puede ver a continuación:

```tsx
type ResponseData = {
  data: string
}

const items: (ResponseData | undefined)[] = [
  { data: 'Banana' },
  undefined,
  { data: 'Dog' },
  undefined
]

const payloads = items.filter(items => item !== undefined)

console.log(payloads)
```

Vamos a guardar nuestro trabajo y a ejecutar nuestro código simplemente para comprobar que este se está comportando tal y como se espera que lo haga:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0a1sb9oo19zz0uvbje5.png)

## Problema

Como podemos observar todo ha funcionado tal y como esperábamos que lo hiciese pero si lo que hacemos es situar el cursor del ratón sobre la declaración del array `payloads` para que Visual Studio Code nos muestre cuál es el tipado que tiene asignado veremos que este seguirá siendo un array de objetos `ResponseData` o `undefined` como se puede ver en la siguiente imagen:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0irw9l3yh216hd766egm.png)

De forma ideal nos gustaría que a partir del filtrado que hemos realizado sea capaz de inferir cuál es el tipo que tendrá asignado a `items` sabiendo que este sería un array formado por elementos que son únicamente del tipo `ResponseData`.

Pero ¿cómo podemos lograr esto? Pues gracias a los **Type Guards** de TypeScript.

## Type Guard

¿Pero en qué consiste crear un Type Guard? Pues en nuestro caso es que a partir de la función que tenemos asignada a la función que estamos pasando como argumento al método `filter()` tenemos que definir el tipo de datos que retornará y esto lo hacemos gracias al uso de la palabra reservada `is` de TypeScript como sigue:

```tsx
const payloads = items.filter(
  (items): item is ResponseData => item !== undefined
)
```

De esta manera que lo que estamos diciéndole a TypeScript es que le podemos asegurar que el resultado de la ejecución de dicha función es del tipo `ResponseData` de tal manera que si ahora ponemos el cursor sobre la definición de la variable `payloads` podemos ver que TypeScript nos indica que será un array de objetos de tipo `ResponseData`:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q149hzusxffo4w251n6m.png)

Es más, para ser mucho más explícitos podemos extraer el Type Guard a una función separada. Así pues vamos a crear una función a la que vamos a denominar `isResponseData` de tal manera que recibirá camo argumento un valor que puede ser de tipo `RespondeData` o `undefined` donde ademanas definiremos como tipo de retorno un **Type Predicate** como sigue:

```tsx
function isResponseData(item: ResponseData | undefined): item is ResponseData {}
```

Dentro de la función `isResponseData` lo que haremos será comprobar si el valor del parámetro es `undefined` o no de tal manera que si lo es retornaremos `true` y en caso contrario retornaremos `false`:

```tsx
function isResponseData(item: ResponseData | undefined): item is ResponseData {
  return item !== undefined
}
```

Con la declaración de nuestro Type Guard como una función ahora vamos a podérsela pasar como argumento al método `filter()` dejándonos el siguiente código completo donde mostramos todos los cambios que hemos realizado hasta este momento:

```tsx
type ResponseData = {
  data: string
}

const items: (ResponseData | undefined)[] = [
  { data: 'Banana' },
  undefined,
  { data: 'Dog' },
  undefined
]

function isResponseData(item: ResponseData | undefined): item is ResponseData {
  return item !== undefined
}

const payloads = items.filter(isResponseData)

console.log(payloads)
```

Si ahora guardamos nuestros cambios y volvemos a ejecutar nuestro código veremos que efectivamente nos volverá a mostrar por la consola un nuevo array de elementos que incluirán únicamente todos aquellos que no son `undefined` como se puede ver en la siguiente imagen:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhe0y4q87yl0q4yk6pfr.png)

y no solamente eso sino que además si ponemos el cursor dentro de Visual Studio Code sobre la variable `payloads` vemos que TypeScript es capaz de indicarnos que es del tipo `ResponseData[]` tal y como esperábamos:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7zzopnodtb97ucxna7km.png)

## Desventajas del uso de los Type Guards

Vamos a ver ahora las desventajas que están asociados al uso de los Type Guards tal y como hemos hecho hasta ahora y el principal error es que a la hora de definir el **Type Predicate** que está asociado a la función que ejerce de **Type Guard** es posible que la podamos definir con un tipo de datos que no le corresponda.

Por poner un ejemplo más claro a partir del código que hemos estado utilizando a lo largo de esta explicación, lo que tratamos de decir es que es posible declarar la función `isResponseData` como sigue:

```tsx
function isResponseData(item: ResponseData | undefined): item is undefined {
  return item !== undefined
}
```

y en este caso el tipo de datos que se inferirá para la variable `payloads` será un array de elementos `undefined` como podemos imaginarnos a partir de lo que acabamos de definir:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zv1hk8d4lm2z1pw8pxkn.png)

es decir que estamos diciendo que el array de los elementos que retorna `isResponseData` es una array de elementos `undefined` cuando viendo el código de esta función podemos estar seguros de que no es así. ¿Qué conclusión podemos sacar de esto? Pues que el **Type Predicate** que hemos utiliado en la definición de nuestro Type Guard sobreescribirá la inferencia de tipos que pueda llegar a hacer TypeScript:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yala1ica96wcg4ph7h24.png)

Esto además es un problema desde el punto de vista del desarrollo porque mientras estamos construyendo nuestro software TypeScript nos dejará trabajar con `payloads` teniendo en cuenta que lo interpretará como un array de `undefined` pese a que esto no es realmente así siendo esta la razón por la que se recomienda que todos los **Type Predicates** que tengamos declarados en nuestro código siempre estén supervisados por tests.

## Asertion Functions

Vamos a ver ahora otro caso que será la implementación de una **Asertion Function** para lo cual deberemos hacer uso de la palabra `assert` que nos proporciona TypeScript que puede transformar un Type Guard en una **Type Assertion**. Pero ¿cómo se hace esto en el código? Pues vamos a verlo en nuestro ejemplo:

```tsx
function isResponseData(item: ResponseData | undefined): assert item is ResponseData {
}
```

Y no solamente eso sino que además tendremos que cambiar la implementación de nuestro `isResponseData` porque en lugar de retornar un valor booleano deberemos lanzar un `Error` en el caso de que no se cumpla la condición que se quiere comprobar con la ejecución de la función. ¿Qué quiere esto decir? Pues que en nuestro caso el código de la **Type Assertion** sería algo como lo siguiente:

```tsx
function isResponseData(item: ResponseData | undefined): assert item is ResponseData {
  if (item === undefined) {
    throw new Error('It is undefined')
  }
}
```

De esta manera el compilador de TypeScript es capaz de deducir que en el caso de que se pase la vetificación que se lleva a cabo en el **Type Assertion** (es decir, que no se lanza un error), entonoces se tipo de datos que tendrá asociado será un `ResponseData` gracias a cómo está declarado el código dentro de la función.

Ahora bien, aunque esto es correcto no es una buena aproximación a seguir para ser utilizada como una argumento del método `filter()` puesto que lanzará un `Error` si no se cumple la condición y por lo tanto detendrá la ejecución del programa en el caso de que no sea capturado:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0d1k97m8294etfcuk3b0.png)

> **Nota:** es importante tener en cuenta que los **Type Assertions** pueden lanzar errores en tiempo de ejecución y por lo tanto interrumpir la ejecución de nuestros programas mientras que los Type Guards lo que pueden llegar a provocar es que se produzcan errores en tiempo de construcción.

Por lo tanto ¿Cuándo deberíamos usar una Assertion Function? Pues sin duda alguna cuando tengamos que realizar algún tipo de validación en tiempo de ejecución mientras que los Type Guard son perfectos para cuando necesitamos hacer un narrowing type (extrechar el tipo de datos) durante el tiempo de compilación.