Utility types: moldando tipos que já existem
Você já tem a interface Produto pronta. Em vez de escrever uma interface nova do zero para cada variação (atualização parcial, resumo, cadastro), o TypeScript deriva essas variações automaticamente.
Estudar esta lição interativa →Times profissionais de TypeScript raramente escrevem uma interface do zero para cada variação de um mesmo dado. Se você já tem interface Produto { id: number; nome: string; preco: number; estoque: number }, e agora precisa de um tipo para "uma atualização parcial de produto" (só alguns campos, os que a pessoa realmente mudou), reescrever tudo com ? em cada propriedade seria repetir informação que já existe. Os utility types resolvem isso: eles pegam um tipo existente e derivam uma variação dele.
Partial<T> transforma todas as propriedades de T em opcionais de uma vez, perfeito para representar uma atualização parcial:
// Partial<T>: todas as propriedades viram opcionais (bom para atualização parcial).
// Omit<T, K>: todas as propriedades de T, exceto as listadas (bom para criação).
interface ProdutoLoja {
id: number;
nome: string;
preco: number;
estoque: number;
}
function atualizarProdutoLoja(produto: ProdutoLoja, mudancas: Partial<ProdutoLoja>): ProdutoLoja {
return { ...produto, ...mudancas };
}
function criarProdutoLoja(dados: Omit<ProdutoLoja, "id">, novoId: number): ProdutoLoja {
return { id: novoId, ...dados };
}
const mouseLoja: ProdutoLoja = { id: 1, nome: "Mouse", preco: 89.9, estoque: 10 };
console.log(atualizarProdutoLoja(mouseLoja, { preco: 79.9 }));
console.log(criarProdutoLoja({ nome: "Teclado", preco: 149.9, estoque: 5 }, 2));Partial e Omit, dois usos comuns num fluxo de cadastro e edição.
No mesmo exemplo, repare em Omit<ProdutoLoja, "id">: ele devolve todas as propriedades de ProdutoLoja menos a listada, perfeito para o momento de criar um produto novo, quando o id ainda nem existe (normalmente é o banco de dados quem gera). O oposto de Omit é Pick<T, K>: em vez de excluir, ele seleciona só as propriedades listadas, útil para um "resumo" que não precisa do objeto inteiro:
// Pick<T, K>: só as propriedades listadas (bom para um resumo).
// Record<K, V>: um tipo de objeto/mapa, toda chave K tem um valor V.
interface ProdutoCatalogoLoja {
id: number;
nome: string;
preco: number;
estoque: number;
}
type ResumoProduto = Pick<ProdutoCatalogoLoja, "nome" | "preco">;
const resumoProduto: ResumoProduto = { nome: "Mouse", preco: 89.9 };
type ContagemPorStatus = Record<"pendente" | "pago" | "cancelado", number>;
const contagemPedidos: ContagemPorStatus = { pendente: 4, pago: 12, cancelado: 1 };
console.log(resumoProduto, contagemPedidos);Pick seleciona um subconjunto; Record tipa um objeto usado como mapa.
Pense na interface original como uma planta arquitetônica completa de uma casa. Pick é como tirar uma cópia só do cômodo da cozinha, sem o resto da planta. Omit é como entregar a planta inteira, menos uma página específica que ainda não existe. Partial é como marcar, na planta inteira, que qualquer cômodo pode estar "ainda não construído". Nenhum desses processos redesenha a planta original do zero, todos partem dela.
| Utility type | O que faz |
|---|---|
Partial<T> | Todas as propriedades de T viram opcionais |
Pick<T, K> | Só as propriedades listadas em K, o resto é descartado |
Omit<T, K> | Todas as propriedades de T, exceto as listadas em K |
Record<K, V> | Um tipo de objeto/mapa: toda chave do tipo K tem um valor do tipo V |
ReturnType<typeof fn> | O tipo do valor que a função fn devolve, sem repetir a assinatura à mão |
Os cinco utility types mais usados no dia a dia, todos derivados de um tipo (ou função) que já existe.
Se uma função já declara o próprio retorno (ou o TypeScript já o infere), ReturnType<typeof minhaFuncao> reaproveita esse tipo em outro lugar do código, sem você precisar copiar a estrutura manualmente. É especialmente útil quando o retorno é um objeto grande, que mudaria em vários lugares se você tivesse escrito ele duas vezes. Repare no typeof dentro do tipo: em posição de tipo, ele não é o typeof do JavaScript (que devolve um texto como "number"), e sim uma pergunta ao TypeScript: "qual é o tipo desta função?". Como ReturnType espera um tipo de função, e criarUsuario é um valor, o typeof faz a ponte entre os dois.
function criarUsuario(nome: string) {
return { id: 1, nome, ativo: true };
}
// typeof, em posição de TIPO, pergunta "qual o tipo desta função?";
// ReturnType pega o que ela devolve, sem repetir a estrutura à mão.
type Usuario = ReturnType<typeof criarUsuario>; // { id: number; nome: string; ativo: boolean }
const u: Usuario = { id: 2, nome: "Bia", ativo: false };
console.log(u.nome, u.ativo);ReturnType<typeof criarUsuario> dá { id: number; nome: string; ativo: boolean } sem escrever a estrutura de novo.
Um erro comum é achar que Partial<T> "valida" que pelo menos alguma propriedade foi passada. Não valida: um objeto vazio {} também satisfaz Partial<T>, porque toda propriedade virou opcional. E, na configuração padrão, { preco: undefined } também passa: "opcional" admite a propriedade ausente e a propriedade presente com o valor undefined. Partial só descreve o formato possível, a lógica de "pelo menos um campo precisa mudar" continua sendo responsabilidade do seu código, se você precisar dela.
atualizarProdutoParcial(produto, mudancas), tipando mudancas como Partial<ProdutoBase>, que devolve um novo objeto combinando produto com as propriedades de mudancas que foram passadas, todas elas, inclusive o id (dica: { ...produto, ...mudancas }).interface ProdutoBase {
id: number;
nome: string;
preco: number;
}
function atualizarProdutoParcial(produto: ProdutoBase, mudancas: any): ProdutoBase {
// sua vez: troque "any" pelo utility type certo para mudancas,
// e devolva um novo objeto combinando produto com mudancas
}
const mouseBase: ProdutoBase = { id: 1, nome: "Mouse", preco: 89.9 };
console.log(atualizarProdutoParcial(mouseBase, { preco: 79.9 }));
🧩 Este é um exercício com correção automática — abra no app para completá-lo e verificar.