# Aula 3.3 - Unions, narrowing e generics

## Identificação

- **Duração:** 2 horas
- **Tipo:** teoria aplicada e laboratório
- **Entrega:** biblioteca genérica de resultados
- **Laboratório:** [`../exemplos/aula-3.3/index.html`](../exemplos/aula-3.3/index.html)

## Introdução

### O que é uma union?

Uma union descreve um valor que pode pertencer a mais de um tipo:

```ts
let identifier: string | number;

identifier = "task-001";
identifier = 42;
```

O símbolo `|` significa “ou”. Enquanto TypeScript não souber qual alternativa está
presente, somente operações seguras para todas elas ficam disponíveis.

### Union é a mesma coisa que array?

Não:

```ts
string | number
```

Representa um único valor que pode ser string ou número.

```ts
(string | number)[]
```

Representa um array cujos itens podem ser strings ou números.

### O que é uma literal union?

```ts
type TaskStatus = "todo" | "doing" | "done";
```

Ela limita uma string às alternativas conhecidas e permite autocomplete e
verificação de exaustividade.

### O que é narrowing?

Narrowing, ou estreitamento, é o processo de verificar qual alternativa existe
antes de utilizá-la:

```ts
function formatIdentifier(value: string | number): string {
  if (typeof value === "number") {
    return value.toFixed(0);
  }

  return value.toUpperCase();
}
```

Dentro do `if`, `value` é `number`. No caminho restante, é `string`.

### O que é um generic?

Um generic é um parâmetro de tipo:

```ts
function first<T>(values: T[]): T | undefined {
  return values[0];
}
```

`T` representa o tipo dos itens naquela chamada:

```ts
first([1, 2]); // number | undefined
first(["A", "B"]); // string | undefined
```

O generic preserva a relação entre entrada e saída.

### Generic é igual a `any`?

Não:

```ts
function firstAny(values: any[]): any {
  return values[0];
}
```

`any` perde a relação e desativa verificações. O generic mantém o tipo concreto
fornecido ou inferido.

### Onde isso aparece no projeto?

O laboratório modelará os estados de uma operação:

```ts
type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "empty" }
  | { status: "error"; message: string };
```

O campo `status` permitirá narrowing seguro, e `T` permitirá reutilizar a mesma
estrutura com tarefas ou outros dados.

## Objetivos

Ao final da aula, o aluno deverá conseguir:

1. criar unions;
2. diferenciar union e array;
3. utilizar literal unions;
4. estreitar tipos com `typeof`;
5. utilizar igualdade, `in` e `instanceof`;
6. criar type guards;
7. modelar discriminated unions;
8. verificar exaustividade com `never`;
9. criar funções genéricas;
10. aplicar constraints;
11. diferenciar generic, union e `any`;
12. modelar estados impossíveis de representar.

## Pré-requisitos

- Aulas 3.1 e 3.2 concluídas;
- funções, objetos, interfaces e aliases;
- tratamento de erros e estados assíncronos.

## Pergunta orientadora

> Como representar alternativas reais do sistema e preservar relações de tipos sem abandonar a segurança?

## Roteiro sugerido

| Etapa | Duração |
|---|---:|
| Unions e tipos literais | 25 min |
| Técnicas de narrowing | 30 min |
| Discriminated unions | 25 min |
| Generics e constraints | 25 min |
| Laboratório | 10 min |
| Revisão | 5 min |

## 1. Union de primitivas

```ts
function printId(id: string | number): void {
  console.log(id);
}
```

Não podemos chamar diretamente:

```ts
id.toUpperCase();
```

O método não existe em `number`. Primeiro estreitamos.

## 2. Union de literais

```ts
type Priority =
  | "low"
  | "planned"
  | "high"
  | "critical";
```

Vantagens:

- impede textos fora do domínio;
- melhora autocomplete;
- permite mapas completos;
- facilita branches exaustivos.

## 3. Union de objetos

```ts
type SearchResult =
  | { found: true; value: string }
  | { found: false; reason: string };
```

Quando `found` é verdadeiro, `value` existe. Quando falso, `reason` existe.

Isso é melhor que:

```ts
type SearchResult = {
  found: boolean;
  value?: string;
  reason?: string;
};
```

A segunda forma permite estados contraditórios, como `found: true` sem `value`.

## 4. Narrowing com `typeof`

```ts
function normalize(value: string | number): string {
  if (typeof value === "number") {
    return String(value);
  }

  return value.trim();
}
```

`typeof null` é `"object"`, uma particularidade histórica. Ao verificar objetos,
confirme também `value !== null`.

## 5. Narrowing por igualdade

```ts
function renderStatus(
  status: "idle" | "loading" | "done",
): string {
  if (status === "loading") {
    return "Carregando...";
  }

  return status === "done" ? "Concluído" : "Aguardando";
}
```

Comparações removem alternativas incompatíveis.

## 6. Truthiness e seus riscos

```ts
function printLength(value: string | null): number {
  if (value) {
    return value.length;
  }

  return 0;
}
```

String vazia cai no caminho falso. Isso pode ser correto ou pode esconder um caso
que deveria ser tratado separadamente.

Prefira checagem explícita quando vazio é diferente de ausente:

```ts
if (value !== null) {
}
```

## 7. Operador `in`

```ts
type Success = {
  data: string[];
};

type Failure = {
  error: string;
};

function render(result: Success | Failure): string {
  if ("data" in result) {
    return `${result.data.length} itens`;
  }

  return result.error;
}
```

Use quando uma propriedade distingue as alternativas.

## 8. `instanceof`

```ts
function formatDate(value: Date | string): string {
  if (value instanceof Date) {
    return value.toISOString();
  }

  return value;
}
```

`instanceof` verifica a cadeia de protótipos em ambiente de execução. Interfaces não podem ser
usadas com `instanceof`, pois são apagadas.

## 9. Type predicate

```ts
function isTaskStatus(
  value: unknown,
): value is TaskStatus {
  return (
    typeof value === "string" &&
    ["todo", "doing", "done"].includes(value)
  );
}
```

`value is TaskStatus` informa que um retorno `true` estreita o argumento.

O compilador confia no predicate. Uma implementação incorreta cria falsa segurança.

## 10. Assertion function

```ts
function assertTaskStatus(
  value: unknown,
): asserts value is TaskStatus {
  if (!isTaskStatus(value)) {
    throw new Error("Status inválido");
  }
}
```

Após chamar, o fluxo considera o valor `TaskStatus`, desde que a função não lance.

## 11. Discriminated union

```ts
type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; message: string };
```

Cada membro possui a mesma propriedade discriminante `status`, com literal
diferente.

Uso:

```ts
function getMessage<T>(state: LoadState<T>): string {
  switch (state.status) {
    case "idle":
      return "Aguardando";
    case "loading":
      return "Carregando";
    case "success":
      return "Concluído";
    case "error":
      return state.message;
  }
}
```

## 12. Estados impossíveis

Modelo frágil:

```ts
interface RequestState<T> {
  loading: boolean;
  data?: T;
  error?: string;
}
```

Ele permite:

```ts
{
  loading: true,
  data: value,
  error: "Falhou"
}
```

Uma discriminated union define combinações válidas e impede alternativas
contraditórias.

## 13. Exaustividade com `never`

```ts
function assertNever(value: never): never {
  throw new Error(
    `Estado não tratado: ${JSON.stringify(value)}`,
  );
}
```

No `default`:

```ts
default:
  return assertNever(state);
```

Se um novo membro for adicionado à union sem um novo `case`, TypeScript aponta que
`state` não é `never`.

## 14. Parâmetro genérico

```ts
function identity<T>(value: T): T {
  return value;
}
```

Chamada explícita:

```ts
identity<string>("texto");
```

Inferida:

```ts
identity("texto");
```

Prefira inferência quando ela mantém o tipo desejado.

## 15. Relação entre entrada e saída

```ts
function first<T>(values: readonly T[]): T | undefined {
  return values[0];
}
```

O retorno depende do tipo dos itens. Uma union fixa perderia parte dessa relação:

```ts
function first(
  values: (string | number)[],
): string | number | undefined {
}
```

O generic funciona também para `Task`, `User` ou qualquer outro tipo.

## 16. Nomes de parâmetros genéricos

Nomes comuns:

- `T`: type;
- `K`: key;
- `V`: value;
- `E`: error ou element, conforme contexto;
- nomes descritivos: `TData`, `TError`, `TItem`.

Em tipos complexos, nomes descritivos ajudam:

```ts
type Result<TData, TError> =
  | { ok: true; data: TData }
  | { ok: false; error: TError };
```

## 17. Generic com múltiplos parâmetros

```ts
function createPair<TLeft, TRight>(
  left: TLeft,
  right: TRight,
): [TLeft, TRight] {
  return [left, right];
}
```

Retorno:

```ts
const pair = createPair("task", 1);
// [string, number]
```

## 18. Constraints

```ts
function getId<T extends { id: number }>(
  value: T,
): number {
  return value.id;
}
```

`T` pode ser qualquer estrutura que possua `id: number`. O retorno preserva o tipo
concreto quando necessário.

## 19. `keyof`

```ts
function getProperty<T, K extends keyof T>(
  object: T,
  key: K,
): T[K] {
  return object[key];
}
```

`key` só pode ser uma chave do objeto, e o retorno corresponde ao tipo daquela
propriedade.

```ts
getProperty(task, "title"); // string
getProperty(task, "missing"); // erro
```

## 20. Valores padrão em generics

```ts
type ApiResult<TData, TError = string> =
  | { ok: true; data: TData }
  | { ok: false; error: TError };
```

Se `TError` não for informado, será `string`.

## 21. Generic em interface

```ts
interface Page<TItem> {
  items: TItem[];
  total: number;
  page: number;
}

type TaskPage = Page<Task>;
```

Uma estrutura de paginação pode ser reutilizada para diferentes entidades.

## 22. Generics não devem ser decorativos

Este generic não cria relação útil:

```ts
function logValue<T>(value: T): void {
  console.log(value);
}
```

Poderia ser:

```ts
function logValue(value: unknown): void {
  console.log(value);
}
```

Use generic quando o parâmetro aparece em mais de um ponto relevante ou quando
preserva informação para o consumidor.

## 23. Generic ou union?

Use union quando as alternativas fazem parte do domínio:

```ts
type Status = "todo" | "done";
```

Use generic quando a estrutura funciona com diferentes tipos mantendo relações:

```ts
type Page<T> = {
  items: T[];
};
```

Eles podem trabalhar juntos:

```ts
type Result<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };
```

## 24. Biblioteca do laboratório

```ts
type LoadState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "empty" }
  | { status: "error"; message: string };
```

A mesma estrutura pode ser:

```ts
LoadState<Task[]>
LoadState<User[]>
LoadState<Course>
```

## 25. Laboratório guiado

Abra a [biblioteca de estados](../exemplos/aula-3.3/index.html).

### Etapa 1 — Alterne cenários

Execute sucesso, vazio e erro. Observe os campos disponíveis em cada estado.

### Etapa 2 — Acompanhe o narrowing

O painel mostra qual `case` do `switch` foi executado:

- `success` permite acessar `data`;
- `error` permite acessar `message`;
- `empty` não contém nenhum dos dois.

### Etapa 3 — Compare payloads

O exemplo usa `LoadState<Task[]>`, mas a biblioteca não conhece `Task`
internamente. O generic preserva o payload.

### Etapa 4 — Examine a exaustividade

Adicione um estado `"cancelled"` à fonte TypeScript sem criar um `case`. Observe o
erro esperado no `assertNever`.

## 26. Erros comuns

### Usar propriedade sem narrowing

```ts
state.data;
```

`data` não existe em todos os membros.

### Criar type guard que sempre retorna true

O compilador confia no predicate; a implementação precisa validar de verdade.

### Usar `as` no lugar de narrowing

Assertions silenciam o compilador, não provam a alternativa.

### Transformar tudo em generic

Um generic sem relação útil aumenta complexidade.

### Esquecer retorno possivelmente `undefined`

`first([])` não possui item. O contrato precisa representar isso.

## 27. Boas práticas

- use literal unions para alternativas fechadas;
- escolha um discriminante claro;
- modele estados válidos, não combinações de flags;
- faça narrowing por verificações reais;
- implemente type guards com cuidado;
- use exaustividade;
- crie generics para preservar relações;
- aplique constraints mínimas;
- prefira inferência;
- evite `any` e assertions desnecessárias.

## 28. Exercícios

### Exercício 1 — Union

Crie `Identifier = string | number` e formate com `typeof`.

### Exercício 2 — Resultado

Modele sucesso e falha com discriminante `ok`.

### Exercício 3 — Type guard

Crie `isTaskStatus(value: unknown)`.

### Exercício 4 — Generic

Crie `last<T>(values: readonly T[]): T | undefined`.

### Exercício 5 — Constraint

Crie uma função que aceita qualquer objeto com `id`.

## 29. Desafio

Modele:

- `Result<TData, TError>`;
- `Page<TItem>`;
- `LoadState<T>`;
- renderização exaustiva;
- validador de `unknown`;
- função `getProperty<T, K extends keyof T>`.

Inclua um teste de compilação esperado para estado não tratado.

## 30. Lista de verificação de conclusão

- [ ] Sei diferenciar union e array.
- [ ] Uso literal unions para domínio fechado.
- [ ] Consigo estreitar com `typeof`, `in` e `instanceof`.
- [ ] Sei criar um type predicate.
- [ ] Modelo estados com discriminated union.
- [ ] Verifico exaustividade com `never`.
- [ ] Entendo a relação preservada por generic.
- [ ] Sei aplicar constraint.
- [ ] Diferencio generic, union e `any`.
- [ ] Não uso assertion no lugar de prova.

## 31. Critérios de avaliação

| Critério | Pontos |
|---|---:|
| Modelagem das unions | 20 |
| Narrowing correto | 20 |
| Estado discriminado | 20 |
| Generic e relação de tipos | 20 |
| Exaustividade | 10 |
| Validação em ambiente de execução | 10 |
| **Total** | **100** |

## Resumo

Nesta aula, aprendemos que:

- unions representam alternativas;
- narrowing prova qual alternativa está presente;
- discriminated unions impedem estados contraditórios;
- `never` ajuda a verificar exaustividade;
- generics preservam relações entre tipos;
- constraints definem requisitos mínimos;
- generic não é `any`;
- type guards precisam corresponder à validação real.

## Próxima aula

Na Aula 3.4, configuraremos o compilador em modo estrito e criaremos um fluxo de
qualidade com lint, formatação e comandos reproduzíveis.
