TypeScript

Como Usar Keyof, Typeof e Indexed Access Types em TypeScript em Produção

10 min de leitura

Como Usar Keyof, Typeof e Indexed Access Types em TypeScript em Produção

Entendendo Keyof em TypeScript O operador é 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 a um tipo, recebe um novo tipo que representa todas as propriedades possíveis daquele objeto. A utilidade prática do 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. Casos de Uso Práticos com Keyof 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 com genéricos permite que você acesse propriedades de forma type-safe, mantendo a inteligência do editor de código funcionando

<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 }) =&gt; {

if (opcoes?.verbose) {

console.log(Processando ${dados.length} itens);

}

return dados.map(d =&gt; d.toUpperCase());

};

type ProcessarDadosType = typeof processarDados;

const meuCallback: ProcessarDadosType = (dados, opcoes) =&gt; {

console.log(&quot;Executando callback&quot;);

return dados.map(d =&gt; [${d}]);

};

// Com classes

class RepositorioUsuario {

async buscarPorId(id: number) {

return { id, nome: &quot;Ana&quot; };

}

}

type RepositorioType = typeof RepositorioUsuario;

const instancia: InstanceType&lt;RepositorioType&gt; = 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 &quot;subscrevendo&quot; 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&lt;{ id: number; nome: string }&gt; };

mensagem: string;

}

type DadosAPI = APIResponse[&quot;dados&quot;]; // { usuarios: Array&lt;{ id: number; nome: string }&gt; }

type UsuariosAPI = APIResponse[&quot;dados&quot;][&quot;usuarios&quot;]; // Array&lt;{ id: number; nome: string }&gt;

type UsuarioAPI = APIResponse[&quot;dados&quot;][&quot;usuarios&quot;][number]; // { id: number; nome: string }

// Criando uma função genérica que extrai tipos

function extrairPropriedade&lt;T, K extends keyof T&gt;(

objeto: T,

chave: K

): T[K] {

return objeto[chave];

}

const resposta: APIResponse = {

status: 200,

dados: {

usuarios: [

{ id: 1, nome: &quot;Maria&quot; },

{ id: 2, nome: &quot;Pedro&quot; }

]

},

mensagem: &quot;Sucesso&quot;

};

const usuarios = extrairPropriedade(resposta, &quot;dados&quot;); // tipo inferido como { usuarios: ... }

const status = extrairPropriedade(resposta, &quot;status&quot;); // 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&lt;T&gt; = {

[K in keyof T as get${Capitalize&lt;string &amp; K&gt;}]: () =&gt; T[K];

};

type FormularioGetters = Getters&lt;Formulario&gt;;

// Resultado:

// {

// getNome: () =&gt; string;

// getEmail: () =&gt; string;

// getIdade: () =&gt; number;

// getAtivo: () =&gt; boolean;

// }

class FormularioImpl implements FormularioGetters {

private _nome = &quot;&quot;;

private _email = &quot;&quot;;

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 =&gt; typeof valor === &quot;number&quot; &amp;&amp; valor &gt; 0,

nome: (valor: any): valor is string =&gt; typeof valor === &quot;string&quot; &amp;&amp; valor.length &gt; 0,

email: (valor: any): valor is string =&gt; /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(valor),

dataNascimento: (valor: any): valor is Date =&gt; valor instanceof Date

};

type ValidadoresType = typeof validadores;

function validarPropriedade&lt;T, K extends keyof T&gt;(

tipo: T,

propriedade: K,

valor: unknown,

validador: ValidadoresType[K &amp; keyof ValidadoresType]

): valor is T[K] {

return validador(valor) as boolean;

}

const usuarioInput = {

id: 1,

nome: &quot;Carlos&quot;,

email: &quot;carlos@email.com&quot;,

dataNascimento: new Date(&quot;1990-05-15&quot;)

};

// Verificação type-safe

if (validarPropriedade(usuarioInput, &quot;id&quot;, 1, validadores.id)) {

console.log(&quot;ID válido&quot;);

}

// Função de alto nível que combina os três conceitos

function criarValidadorGenerico&lt;T&gt;(objeto: T, schema: { [K in keyof T]: (v: unknown) =&gt; v is T[K] }) {

return function validar(dados: Partial&lt;T&gt;): dados is T {

return (Object.keys(schema) as (keyof T)[]).every(chave =&gt; {

if (!(chave in dados)) return false;

return schema[chave](dados[chave]);

});

};

}

const validadorUsuario = criarValidadorGenerico(usuarioInput, validadores);

const dadosTeste: Partial&lt;Usuario&gt; = usuarioInput;

if (validadorUsuario(dadosTeste)) {

console.log(&quot;Usuário válido!&quot;);

}</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática
Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática

Fundamentos de Funções em TypeScript Uma função em TypeScript é bem mais que...

Event-Driven Architecture com TypeScript: Events e Subscribers Tipados na Prática
Event-Driven Architecture com TypeScript: Events e Subscribers Tipados na Prática

O Que é Event-Driven Architecture? A Event-Driven Architecture é um padrão ar...

Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais
Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais

Introdução aos Modificadores de Acesso em TypeScript Os modificadores de aces...