keyof, as const, satisfies, Readonly e Promise
Cinco ferramentas pequenas que aparecem em quase todo código TypeScript profissional e que ligam tudo o que você viu: chaves de um tipo, listas que viram tipos, valores que não mudam e promessas tipadas.
Estudar esta lição interativa →Esta lição junta peças curtas que resolvem problemas bem concretos. A primeira: como escrever uma função que lê "um campo qualquer" de um produto, aceitando só campos que existem e devolvendo o tipo certo de cada um? O operador keyof gera a união das chaves de um tipo (keyof Produto vira "id" | "nome" | "preco"), e Produto[K] é o tipo do campo escolhido. Combinados com um generic, o retorno acompanha o argumento:
interface Produto {
id: number;
nome: string;
preco: number;
}
// keyof Produto é a união "id" | "nome" | "preco".
// Produto[K] é o tipo do campo escolhido: o retorno acompanha o argumento.
function ler<K extends keyof Produto>(p: Produto, campo: K): Produto[K] {
return p[campo];
}
const caneta: Produto = { id: 1, nome: "Caneta", preco: 3.5 };
const nome = ler(caneta, "nome"); // string
const preco = ler(caneta, "preco"); // number
console.log(nome.toUpperCase(), preco.toFixed(2));
// ler(caneta, "cor"); // erro: "cor" não é uma chave de ProdutoA segunda peça resolve a repetição de escrever uma lista de valores e, em outro lugar, o union type com os mesmos valores. as const trata a lista como um conjunto de literais somente leitura, e (typeof STATUS)[number] extrai o union dela. O satisfies confere se um valor tem o formato esperado sem apagar o tipo específico dele (uma anotação : Record<string, string> apagaria). E Readonly e readonly impedem alterações depois da criação:
// as const congela os valores como literais (e somente leitura)...
const STATUS = ["pendente", "pago", "cancelado"] as const;
// ...e permite derivar o tipo da própria lista, sem escrever tudo duas vezes.
type Status = (typeof STATUS)[number]; // "pendente" | "pago" | "cancelado"
const atual: Status = "pago";
console.log(STATUS.length, atual);
// const errado: Status = "enviado"; // erro: não está na lista
// satisfies confere o formato SEM perder o tipo específico do valor:
const tema = { fundo: "#fff", texto: "#111" } satisfies Record<string, string>;
console.log(tema.fundo.toUpperCase()); // tema.fundo continua sendo string
// Readonly e readonly: impedem a alteração depois de criado.
const config: Readonly<{ porta: number }> = { porta: 3000 };
// config.porta = 80; // erro TS2540: propriedade somente leitura
const notas: readonly number[] = [7, 8, 9];
// notas.push(10); // erro: "push" não existe em readonly number[]
console.log(config.porta, notas.length);Descomente as linhas de erro, uma de cada vez, para ver cada diagnóstico.
readonly e Readonly existem só enquanto o TypeScript checa (somem no JavaScript gerado, como todo tipo) e travam apenas o primeiro nível: um Readonly<{ itens: string[] }> não deixa trocar a propriedade itens, mas deixa dar push na lista que está dentro dela.
Por fim, o código assíncrono do curso de JavaScript também tem tipos. Uma função async sempre devolve uma Promise, e o tipo escrito entre os sinais de menor e maior diz o que sai dela quando termina: Promise<string> é uma promessa de string. Depois do await (ou dentro do .then), o valor já é a string, e o TypeScript sabe:
function esperar(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// async devolve sempre uma Promise: aqui, Promise<string>.
async function buscarNome(id: number): Promise<string> {
await esperar(10);
return "usuario-" + id;
}
buscarNome(1).then((nome) => {
console.log(nome.toUpperCase()); // nome é string, não Promise
});