Entendendo e Utilizando Tipos Condicionais (TypeScript)
Tags: typescript, beginners, programming
Published: 2024-06-04 02:42:03

## Introdução
Dentre as diversas ferramentas de tipagem avançada que o TypeScript provê, **tipos condicionais** são uma das mais importantes, pois nos dá habilidade de criar **tipos não uniformes**, ou seja, que podem variar conforme a entrada.

Sua sintaxe é análoga a do operador ternário do TypeScript, sendo que, a condição deve expressar um teste de relação entre os tipos.

Exemplo lado a lado:

```tsx
/*
* A condição pode ser qualquer tipo de operação que
* resulte em um boleano.
*/
const ternary = condition ? expr1 : expr2;

/*
* A condição deve ser criada testando a relação entre
* os dois tipos, sempre utilizando o operador `extends`.
*/
type Conditional<T, U> = T extends U ? string : number;
```
## Condições simples
```tsx
type IsString<T> = T extends string ? true : false;

type R1 = IsString<'Hello'>;
// => true

type R2 = IsString<2>;
// => false
```

No exemplo acima, criamos o tipo `IsString` que verifica se `T` é atribuível a `string`, retornando `true` caso verdadeiro, ou `false` caso contrário.

Agora, observe atentamente o exemplo abaixo:

```tsx
type Is2020<T> = T extends true ? 'yes' : 'no';

const year = new Date().getFullYear() === 2020;

type R = Is2020<typeof year>;
// => 'yes' | 'no'
```

Indo direto para a última linha do exemplo, note que `Is2020<typeof year>` está retornando como resultado uma união entre os dois tipos possíveis especificados na sua assinatura, mas por quê? Para responder essa pergunta, temos que ter em mente que o TypeScript roda em tempo de desenvolvimento e não consegue inferir tipos de runtime, que no nosso caso, é o trecho `new Date().getFullYear() === 2020`. Como inferir não foi possível, recebemos como resultado uma **união** com todas as possibilidades.
## Condições encadeadas
Assim como um ternário comum, podemos encadear tipos condicionais e criar tipagens mais robustas baseadas em múltiplas regras.

```tsx
type IsFrontEndFramework<T> = T extends string
? T extends 'Angular'
? true
: T extends 'Vue'
? true
: T extends 'React'
? true
: 'Insert a valid framework.'
: never;

type R1 = IsFrontEndFramework<'React'>;
// => true

type R2 = IsFrontEndFramework<'Hello'>;
// => 'Insert a valid framework.'

type R3 = IsFrontEndFramework<920>;
// => never
```
## `Infer`indo Tipos
Até agora vimos que é possível encadear e criar condições variadas que cobrem uma infinidade de cenários, porém, em certos casos, ainda é muito trabalhoso ou praticamente impossível criar a *constraint* correta apenas utilizando condições simples. Para esses cenários mais complexos, temos a *keyword* `infer`, que auxilia na inferência de tipos dinâmicos dentro das nossas condições. A única **limitação** aqui é: você só poderá utilizar `infer` numa condição `extends`.
### Array
Vamos observar o exemplo a seguir, onde utilizamos `infer` para inferir o tipo de um array:

```typescript
type InferArrayType<A> = A extends Array<infer U> ? U : never;

type R1 = InferArrayType<Array<string>>;
// => string

type R2 = InferArrayType<Array<number>>;
// => number

type R3 = InferArrayType<Array<Array<string>>>;
// => string[]
```

Atenção no `InferArrayType`, um tipo que recebe um genérico `A`, valida se este tipo é um array e, caso seja, retorna o tipo do elemento do array. Note que o tipo `U` é inferido dinamicamente utilizando a *keyword* `infer`.
Esse é um exemplo simples, mas que demonstra a potência do `infer` em situações mais complexas.
Agora, vamos ver um exemplo mais complexo, onde utilizamos `infer` para converter um objeto de duas propriedades em uma tupla:

```typescript
type ObjectToTuple<T> = T extends { a: infer A; b: infer B } ? [A, B] : never;

type Result = ObjectToTuple<{a: string; b: number}>;
// => [string, number]
```

No exemplo acima, `ObjectToTuple` é um tipo que recebe um genérico `T`, valida se `T` é um objeto com as propriedades `a` e `b`, e retorna um array com os tipos das propriedades `a` e `b`. Note que `A` e `B` são inferidos dinamicamente utilizando a *keyword* `infer`.
### Inferindo parâmetros ou retorno de funções
A versatilidade do `infer` vai além da inferência de tipos simples, também sendo possível inferir tipos de funções. No exemplo abaixo, iremos inferir o tipo do parâmetro de uma função de aridade 1 e o tipo de retorno da função.

```typescript
type FunctionArg<F> = F extends (p: infer P) => unknown ? P : never;

type R1 = FunctionArg<(x: number) => boolean>;
// => number

type R2 = FunctionArg<(x: number, y: number) => boolean>;
// => never
```

Neste exemplo, a função `FunctionArg` recebe um tipo genérico `F` e verifica se `F` é uma função de aridade 1. Caso seja, o tipo do parâmetro é inferido e retornado. Caso a aridade seja maior que 1, o tipo `never` é retornado.

Mas e se quisermos inferir os tipos de uma função com aridade maior que 1? Podemos fazer isso com a seguinte implementação:

```typescript
type FunctionArg<F> = F extends (...args: infer P) => unknown ? P : never;

type R1 = FunctionArg<(x: number) => boolean>;
// => [number]

type R2 = FunctionArg<(x: number, y: number) => boolean>;
// => [number, number]
```

Neste caso, a função `FunctionArg` recebe um tipo genérico `F` e verifica se `F` é uma função de aridade `n`. Caso seja, o tipo dos parâmetros é inferido e retornado em um array.

Para inferir o tipo de retorno de uma função, podemos fazer o seguinte:

```typescript
type FunctionReturn<F> = F extends (...args: any[]) => infer R ? R : never;

type R1 = FunctionReturn<(x: number) => boolean>;
// => boolean

type R2 = FunctionReturn<(x: number, y: number) => boolean>;
// => boolean
```

Neste caso, a função `FunctionReturn` recebe um tipo genérico `F` e verifica se `F` é uma função de aridade `n`. Caso seja, o tipo de retorno é inferido e retornado.
## Importante
- Apesar de ser uma ferramenta poderosa, tipos condicionais podem ser complexos e difíceis de entender, especialmente quando combinados com `infer`.
- É de extrema importância avaliar as necessidades de cada caso para decidir se é ou não necessário.
- Tipos condicionais mal escritos podem piorar a experiência de desenvolvimento e tornar o código mais difícil de entender. Use com moderação e sempre busque a simplicidade.

## Conclusão
Tipos condicionais são uma ferramenta poderosa para criar tipos não uniformes e mais robustos. A *keyword* `infer` é uma ferramenta essencial para inferir tipos dinâmicos dentro de condições. Combinando essas duas ferramentas, podemos criar tipos complexos e expressivos que cobrem uma grande variedade de cenários e que aumentam a segurança e a robustez do nosso código.