FNSP Web Page

Os tipos utilitários que valem a pena (e os que só complicam)

Há um tipo neste projeto que paga sozinho o preço de aprender isto:

export type Sondagem = NonNullable<Awaited<ReturnType<typeof lerSondagem>>>;

Lê-se de dentro para fora: o que a função devolve, retirado de dentro da promise, com o null removido. Três utilitários encadeados, uma linha, e zero manutenção.

1. Derivar em vez de escrever

A alternativa seria escrever a forma à mão — doze campos, com os tipos todos. Funciona hoje. E no dia em que a consulta passar a trazer outro campo, há duas descrições da mesma coisa, e uma delas está errada sem avisar.

É esta a ideia que torna os utilitários úteis, e não a economia de escrita: a forma dos dados tem um dono. Aqui o dono é a função que consulta, porque é ela que decide que campos existem. Tudo o resto deve ser derivado dela — e então deixa de poder divergir.

2. Pick e Omit, e a assimetria que ninguém conta

type Resumo = Pick<Artigo, 'id' | 'titulo'>;
type SemCorpo = Omit<Artigo, 'corpo'>;

Parecem simétricos e não são. O Pick dá erro se o campo não existir. O Omit não: Omit<Artigo, 'titlo'> compila alegremente e não remove nada.

Ou seja, um erro de escrita num Omit é silencioso, e o tipo resultante tem um campo a mais do que se pensava. É a diferença entre os dois que mais vezes morde, e por isso o Pick é preferível sempre que a lista de campos a manter for curta.

3. Record, com uma ressalva

É o mais usado aqui — seis sítios, para mapas de nome para valor. A ressalva liga-se ao artigo das opções do strict:

const mapa: Record<string, string> = { a: '1' };
const x = mapa['inexistente'];  // tipo: string. Valor: undefined.

O Record<string, T> afirma que qualquer chave existe, o que é falso por definição num mapa. Com noUncheckedIndexedAccess desligado, o compilador concorda com a mentira. Para chaves conhecidas, Record<'a' | 'b', T> diz a verdade e apanha a chave inventada.

4. O Partial que quase sempre está errado

// Nao: agora nada e obrigatorio, nem o id
function actualizar(dados: Partial<Artigo>) {}

// Sim: o que e obrigatorio continua a ser
function actualizar(id: number, dados: Partial<Omit<Artigo, 'id'>>) {}

O Partial é a ferramenta certa para uma actualização parcial e a errada para um parâmetro de função, porque torna opcional também o que tem de estar lá. O resultado é um tipo que deixa passar chamadas sem sentido, e verificações em execução a compensar.

5. Os que servem para embrulhar código de outros

Parameters<T>, ReturnType<T> e Awaited<T> ganham quando se escreve algo em volta de uma função que não é nossa: um wrapper com registo, uma versão em cache, um retry. Repetir a assinatura à mão é assinar a manutenção dela; derivá-la é acompanhar automaticamente quem a muda.

6. Os que só complicam

A ginástica de tipos condicionais recursivos, os que se escrevem para provar que é possível. Há um teste simples: se for preciso ler o tipo para entender o que a função recebe, o tipo falhou. Era para explicar, e passou a precisar de explicação.

Nesses casos um tipo mais simples, ainda que menos exacto, mais uma verificação explícita — da família dos type guards — resolve melhor e lê-se daqui a um ano.

O resumo

Derivar da fonte de verdade, que é quase sempre a função que traz os dados. Preferir Pick a Omit, porque um engano no segundo é silencioso. Desconfiar do Record<string, T> e do Partial em parâmetros. E parar antes da ginástica: um tipo existe para dispensar explicação, não para a exigir.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.