<h2>Entendendo Keyof em TypeScript</h2>
<p>O operador <code>keyof</code> é um dos recursos mais poderosos do TypeScript para trabalhar com tipos de forma dinâmica e segura. Ele extrai as chaves de um objeto ou interface e as transforma em um tipo união literal. Quando você aplica <code>keyof</code> a um tipo, recebe um novo tipo que representa todas as propriedades possíveis daquele objeto.</p>
<p>A utilidade prática do <code>keyof</code> aparece quando você precisa garantir que uma string ou variável corresponda exatamente a uma das propriedades existentes. Em vez de aceitar qualquer string, você força o sistema de tipos a validar se a chave é válida. Isso elimina erros em tempo de desenvolvimento e torna seu código muito mais robusto.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Casos de Uso Práticos com Keyof</h3>
<p>Um padrão muito comum é criar funções genéricas que trabalham com propriedades de um objeto sem perder a segurança de tipos. A combinação de <code>keyof</code> com genéricos permite que você acesse propriedades de forma type-safe, mantendo a inteligência do editor de código funcionando perfeitamente.</p>
<pre><code class="language-typescript"></code></pre>
<p>---</p>
<h2>Typeof: Inferindo Tipos de Valores</h2>
<p>O <code>typeof</code> em TypeScript funciona de forma diferente do JavaScript puro. Enquanto em JavaScript ele retorna uma string em tempo de execução, no TypeScript ele é um operador de tipo que funciona em tempo de compilação. Ele permite extrair o tipo de qualquer expressão, variável ou valor e usá-lo como um tipo.</p>
<p>Essa funcionalidade é especialmente valiosa quando você tem dados complexos cujo tipo não foi explicitamente definido, mas você quer garantir que outras partes do código respeitem esse tipo automaticamente. Em vez de reescrever a interface manualmente, o <code>typeof</code> infere o tipo para você.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Typeof com Funções e Classes</h3>
<p>O <code>typeof</code> também extrai o tipo de funções e construtores, sendo extremamente útil para trabalhar com callbacks, factories e padrões mais avançados. Você pode garantir que uma função receba o tipo correto de callback sem repetir sua assinatura.</p>
<pre><code class="language-typescript">const processarDados = (dados: string[], opcoes?: { verbose?: boolean }) => {
if (opcoes?.verbose) {
console.log(Processando ${dados.length} itens);
}
return dados.map(d => d.toUpperCase());
};
type ProcessarDadosType = typeof processarDados;
const meuCallback: ProcessarDadosType = (dados, opcoes) => {
console.log("Executando callback");
return dados.map(d => [${d}]);
};
// Com classes
class RepositorioUsuario {
async buscarPorId(id: number) {
return { id, nome: "Ana" };
}
}
type RepositorioType = typeof RepositorioUsuario;
const instancia: InstanceType<RepositorioType> = new RepositorioUsuario();</code></pre>
<p>---</p>
<h2>Indexed Access Types: Acessando Tipos Dinamicamente</h2>
<p>Os tipos de acesso indexado (Indexed Access Types) permitem que você acesse o tipo de uma propriedade específica de outro tipo usando a notação de colchetes. É como se você estivesse "subscrevendo" um tipo para obter o tipo de uma de suas propriedades. Esse recurso é fundamental para criar código genérico que se adapta aos dados que você está manipulando.</p>
<p>Quando combinado com <code>keyof</code>, o acesso indexado cria possibilidades extraordinárias para transformações de tipo. Você pode mapear tipos, validar correspondências entre chaves e valores, e construir abstrações poderosas que antes exigiriam código repetido.</p>
<pre><code class="language-typescript"></code></pre>
<h3>Padrões Avançados com Acesso Indexado</h3>
<p>A verdadeira potência emerge quando você combina acesso indexado com genéricos. Você pode criar funções que extraem tipos de valores dinamicamente, mapeiam estruturas e criam abstrações que escalam com segurança de tipos.</p>
<pre><code class="language-typescript">interface APIResponse {
status: number;
dados: { usuarios: Array<{ id: number; nome: string }> };
mensagem: string;
}
type DadosAPI = APIResponse["dados"]; // { usuarios: Array<{ id: number; nome: string }> }
type UsuariosAPI = APIResponse["dados"]["usuarios"]; // Array<{ id: number; nome: string }>
type UsuarioAPI = APIResponse["dados"]["usuarios"][number]; // { id: number; nome: string }
// Criando uma função genérica que extrai tipos
function extrairPropriedade<T, K extends keyof T>(
objeto: T,
chave: K
): T[K] {
return objeto[chave];
}
const resposta: APIResponse = {
status: 200,
dados: {
usuarios: [
{ id: 1, nome: "Maria" },
{ id: 2, nome: "Pedro" }
]
},
mensagem: "Sucesso"
};
const usuarios = extrairPropriedade(resposta, "dados"); // tipo inferido como { usuarios: ... }
const status = extrairPropriedade(resposta, "status"); // tipo inferido como number</code></pre>
<h3>Mapeando Tipos com Acesso Indexado</h3>
<p>Um padrão extremamente prático é criar tipos mapeados que transformam todas as propriedades de um tipo. Você pode converter um objeto em suas versões getters, setters, observáveis ou qualquer outra transformação, mantendo a segurança de tipos para cada propriedade.</p>
<pre><code class="language-typescript">interface Formulario {
nome: string;
email: string;
idade: number;
ativo: boolean;
}
// Tipo que converte todas as propriedades em getters
type Getters<T> = {
[K in keyof T as get${Capitalize<string & K>}]: () => T[K];
};
type FormularioGetters = Getters<Formulario>;
// Resultado:
// {
// getNome: () => string;
// getEmail: () => string;
// getIdade: () => number;
// getAtivo: () => boolean;
// }
class FormularioImpl implements FormularioGetters {
private _nome = "";
private _email = "";
private _idade = 0;
private _ativo = false;
getNome() { return this._nome; }
getEmail() { return this._email; }
getIdade() { return this._idade; }
getAtivo() { return this._ativo; }
}</code></pre>
<p>---</p>
<h2>Integrando os Três Conceitos</h2>
<p>Para dominar completamente esses recursos, é essencial entender como eles funcionam em harmonia. Um padrão real que você encontrará frequentemente envolve usar <code>keyof</code> para identificar chaves válidas, <code>typeof</code> para inferir tipos de objetos dinâmicos, e acesso indexado para extrair o tipo específico de uma propriedade.</p>
<p>Esse trio forma a base de bibliotecas populares como Zod, Prisma e React Query. Entender como combinar esses conceitos permite que você crie abstrações poderosas que escalam com seu projeto sem sacrificar a segurança de tipos.</p>
<pre><code class="language-typescript">// Cenário real: Validador genérico de formulários
interface Usuario {
id: number;
nome: string;
email: string;
dataNascimento: Date;
}
const validadores = {
id: (valor: any): valor is number => typeof valor === "number" && valor > 0,
nome: (valor: any): valor is string => typeof valor === "string" && valor.length > 0,
email: (valor: any): valor is string => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(valor),
dataNascimento: (valor: any): valor is Date => valor instanceof Date
};
type ValidadoresType = typeof validadores;
function validarPropriedade<T, K extends keyof T>(
tipo: T,
propriedade: K,
valor: unknown,
validador: ValidadoresType[K & keyof ValidadoresType]
): valor is T[K] {
return validador(valor) as boolean;
}
const usuarioInput = {
id: 1,
nome: "Carlos",
email: "carlos@email.com",
dataNascimento: new Date("1990-05-15")
};
// Verificação type-safe
if (validarPropriedade(usuarioInput, "id", 1, validadores.id)) {
console.log("ID válido");
}
// Função de alto nível que combina os três conceitos
function criarValidadorGenerico<T>(objeto: T, schema: { [K in keyof T]: (v: unknown) => v is T[K] }) {
return function validar(dados: Partial<T>): dados is T {
return (Object.keys(schema) as (keyof T)[]).every(chave => {
if (!(chave in dados)) return false;
return schema[chave](dados[chave]);
});
};
}
const validadorUsuario = criarValidadorGenerico(usuarioInput, validadores);
const dadosTeste: Partial<Usuario> = usuarioInput;
if (validadorUsuario(dadosTeste)) {
console.log("Usuário válido!");
}</code></pre>
<p>---</p>
<h2>Conclusão</h2>
<p>Nesta aula, você aprendeu que <strong><code>keyof</code> extrai as chaves de um tipo como união literal</strong>, permitindo que funções genéricas validem se uma propriedade realmente existe em um objeto sem sacrificar segurança de tipos. Esse operador é essencial para criar interfaces fluidas entre dados dinâmicos e código tipado.</p>
<p><strong><code>typeof</code> infere o tipo de qualquer expressão ou valor em tempo de compilação</strong>, transformando dados existentes em tipos reutilizáveis sem necessidade de reescrever interfaces manualmente. Ele é seu aliado quando trabalhando com configurações, constantes ou estruturas que já existem no código.</p>
<p><strong>Indexed Access Types</strong> (acesso indexado) permite extrair o tipo de uma propriedade específica usando notação de colchetes, e quando combinado com os anteriores, cria abstrações poderosas para transformação e mapeamento de tipos. Esses três conceitos juntos formam a fundação de padrões avançados em TypeScript que você verá em código profissional de alta qualidade.</p>
<p>---</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/keyof-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Keyof Type Operator</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/typeof-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Typeof Type Operator</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Indexed Access Types</a></li>
<li><a href="https://basarat.gitbook.io/typescript/type-system/index-signatures" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Advanced Types</a></li>
<li><a href="https://zod.dev/?id=type-inference" target="_blank" rel="noopener noreferrer">Zod Documentation - Type Inference</a></li>
</ul>
<p><!-- FIM --></p>