Uniões discriminadas: deixar o compilador contar os casos
Um ecrã que carrega dados tem três estados possíveis, e quase toda a gente os escreve assim:
type Estado = {
aCarregar: boolean;
dados?: Artigo[];
erro?: string;
};O problema deste tipo é que ele permite coisas impossíveis: aCarregar: true com dados e com erro ao mesmo tempo. Nada no compilador impede essa combinação, e é preciso lembrar-se dela em cada if.
1. Um campo que decide o resto
Uma união discriminada é um conjunto de formas alternativas, cada uma com um campo comum que diz qual delas é:
type Estado =
| { tipo: 'aCarregar' }
| { tipo: 'pronto'; artigos: Artigo[] }
| { tipo: 'erro'; mensagem: string };Agora os estados impossíveis deixam de se poder escrever, e o TypeScript sabe, dentro de cada ramo, o que existe:
function mostrar(estado: Estado) {
if (estado.tipo === 'pronto') {
return estado.artigos.length; // aqui, 'artigos' existe de certeza
}
return estado.artigos;
// ~~~~~~~ Erro: não existe no estado 'aCarregar' nem em 'erro'
}2. O truque do never
Esta é a parte que muda a vida. Num switch, atribuir o valor a uma variável do tipo never no ramo final obriga o compilador a confirmar que não sobrou nenhum caso:
function texto(estado: Estado): string {
switch (estado.tipo) {
case 'aCarregar':
return 'A carregar…';
case 'pronto':
return `${estado.artigos.length} artigos`;
case 'erro':
return estado.mensagem;
default:
const naoTratado: never = estado;
return naoTratado;
}
}No dia em que acrescentar um quarto estado ao tipo — { tipo: 'vazio' } — esta função deixa de compilar, com o erro a apontar para o sítio certo:
// Erro: o tipo '{ tipo: "vazio"; }' não é atribuível ao tipo 'never'É a diferença entre descobrir o caso esquecido no compilador e descobri-lo em produção, num ecrã em branco.