TypeScript

Guia Completo de Variância em TypeScript: Covariance, Contravariance e Bivariance

8 min de leitura

Guia Completo de Variância em TypeScript: Covariance, Contravariance e Bivariance

Entendendo Variância: O Problema Fundamental Variância é um conceito que descreve como tipos genéricos se relacionam com seus subtipos. Quando você trabalha com programação orientada a objetos, herança e tipos genéricos em TypeScript, surgem questões cruciais: se é um subtipo de , então é um subtipo de ? A resposta não é tão simples quanto parece, e é aqui que variância entra em cena. O problema emerge quando tentamos atribuir um tipo genérico mais específico a uma variável de um tipo genérico mais amplo. Sem um sistema de variância bem definido, você poderia enfrentar erros em tempo de execução. Por exemplo, se você pudesse adicionar um a um declarado como , violaria a segurança de tipos. TypeScript resolve isso através de covariance, contravariance e bivariance — regras que definem quando uma atribuição genérica é segura. Covariance: Mantendo a Hierarquia de Tipos O Conceito de Covariance Covariance significa que se é um subtipo de , então é um subtipo de .

<h2>Entendendo Variância: O Problema Fundamental</h2>

<p>Variância é um conceito que descreve como tipos genéricos se relacionam com seus subtipos. Quando você trabalha com programação orientada a objetos, herança e tipos genéricos em TypeScript, surgem questões cruciais: se <code>Cachorro</code> é um subtipo de <code>Animal</code>, então <code>Array&lt;Cachorro&gt;</code> é um subtipo de <code>Array&lt;Animal&gt;</code>? A resposta não é tão simples quanto parece, e é aqui que variância entra em cena.</p>

<p>O problema emerge quando tentamos atribuir um tipo genérico mais específico a uma variável de um tipo genérico mais amplo. Sem um sistema de variância bem definido, você poderia enfrentar erros em tempo de execução. Por exemplo, se você pudesse adicionar um <code>Gato</code> a um <code>Array&lt;Animal&gt;</code> declarado como <code>Array&lt;Cachorro&gt;</code>, violaria a segurança de tipos. TypeScript resolve isso através de covariance, contravariance e bivariance — regras que definem quando uma atribuição genérica é segura.</p>

<h2>Covariance: Mantendo a Hierarquia de Tipos</h2>

<h3>O Conceito de Covariance</h3>

<p>Covariance significa que se <code>T</code> é um subtipo de <code>U</code>, então <code>Generic&lt;T&gt;</code> é um subtipo de <code>Generic&lt;U&gt;</code>. Em outras palavras, a relação de herança é <em>preservada</em> no tipo genérico. Isso funciona bem para posições de <strong>saída</strong> (quando você lê dados) porque é seguro retornar algo mais específico do que o esperado.</p>

<pre><code class="language-typescript"></code></pre>

<p>Neste exemplo, <code>Cachorro[]</code> é atribuído a <code>Animal[]</code>. Isso é seguro porque quando você acessa elementos através da referência <code>Animal[]</code>, espera comportamentos de <code>Animal</code>. Como <code>Cachorro</code> é um subtipo e implementa corretamente todos os comportamentos, não há problema.</p>

<pre><code class="language-typescript"></code></pre>

<h3>Limitações e Riscos</h3>

<p>Covariance é segura para leitura, mas perigosa para escrita. Se você tentar adicionar elementos a um array declarado com covariance, pode enfrentar problemas:</p>

<pre><code class="language-typescript">const animais: Animal[] = [new Animal()];

const cachorros: Cachorro[] = animais as Cachorro[]; // ❌ Casting perigoso

cachorros.push(new Gato()); // TypeScript permite, mas é um erro lógico!

// Agora você tem um Gato em um array declarado como Cachorro[]</code></pre>

<h2>Contravariance: Invertendo a Hierarquia</h2>

<h3>O Conceito de Contravariance</h3>

<p>Contravariance é o oposto: se <code>T</code> é um subtipo de <code>U</code>, então <code>Generic&lt;U&gt;</code> é um subtipo de <code>Generic&lt;T&gt;</code>. A relação de herança é <em>invertida</em> no tipo genérico. Isso é seguro em posições de <strong>entrada</strong> (quando você escreve dados) porque é seguro aceitar algo mais geral do que o esperado.</p>

<pre><code class="language-typescript"></code></pre>

<p>Esse código é seguro porque o <code>manipuladorAnimal</code> aceita qualquer <code>Animal</code>, incluindo um <code>Cachorro</code>. Não há risco de tentar acessar propriedades específicas de <code>Cachorro</code> que não existam em <code>Animal</code>.</p>

<pre><code class="language-typescript"></code></pre>

<h3>Compreendendo a Segurança</h3>

<p>Contravariance é segura porque você está <em>aceitando</em> um tipo mais geral. Quem chama a função envia um <code>Cachorro</code> específico, mas o handler sabe tratar qualquer <code>Animal</code>, então funciona. O problema nunca ocorre porque você não pode chamar métodos específicos de <code>Cachorro</code> dentro de um handler que aceita <code>Animal</code>.</p>

<h2>Bivariance: A Flexibilidade Perigosa</h2>

<h3>O Que é Bivariance</h3>

<p>Bivariance significa que um tipo genérico aceita tanto covariance quanto contravariance — não há distinção clara. Se <code>T</code> é um subtipo de <code>U</code>, você pode usar <code>Generic&lt;T&gt;</code> onde <code>Generic&lt;U&gt;</code> é esperado, e vice-versa. Essa flexibilidade é prática, mas compromete a segurança de tipos.</p>

<pre><code class="language-typescript"></code></pre>

<h3>Quando Bivariance Causa Problemas</h3>

<p>A bivariance de TypeScript é principalmente uma decisão de design para manter compatibilidade e praticidade. Arrays em TypeScript são bivariant, o que significa que você pode atribuir <code>Cachorro[]</code> a <code>Animal[]</code> e vice-versa, mas com riscos:</p>

<pre><code class="language-typescript"></code></pre>

<p>TypeScript permite isso sem erro em tempo de compilação porque considera arrays como bidirecionais. Esse é um tradeoff entre segurança e praticidade que você deve conhecer.</p>

<pre><code class="language-typescript">// Como TypeScript resolve: permite leitura e escrita com o mesmo tipo

function processar(lista: Cachorro[]): void {

lista.forEach(c =&gt; c.fazerSom());

}

const listaMista: Animal[] = [new Cachorro(), new Gato()];

processar(listaMista as Cachorro[]); // ❌ Requer casting - perigo!</code></pre>

<h2>Variância em Funções e Métodos</h2>

<h3>Posição do Tipo Genérico Importa</h3>

<p>Em TypeScript, onde você usa um tipo genérico determina a variância aplicável. Posições de entrada (parâmetros) seguem contravariance, posições de saída (retornos) seguem covariance.</p>

<pre><code class="language-typescript"></code></pre>

<pre><code class="language-typescript"></code></pre>

<h3>Métodos de Callback com Múltiplas Posições</h3>

<pre><code class="language-typescript">interface Transformador&lt;T, U&gt; {

transformar(item: T): U;

}

const transformarParaCachorro: Transformador&lt;Animal, Cachorro&gt; = {

transformar(animal: Animal): Cachorro {

return new Cachorro(); // Simplificado para exemplo

}

};

// Entrada: contravariance (Animal é mais geral que Cachorro)

// Saída: covariance (Cachorro é mais específico que Animal)

// Isso é invariant como um todo - deve ser exato ou necessita casting

const transformador: Transformador&lt;Cachorro, Animal&gt; = transformarParaCachorro as any;</code></pre>

<h2>Conclusão</h2>

<p>Neste artigo, você aprendeu que <strong>variância define como tipos genéricos se relacionam com hierarquias de herança</strong>. Covariance preserva a hierarquia e é segura para leitura, contravariance a inverte e é segura para escrita, e bivariance oferece flexibilidade com riscos. O ponto crítico é reconhecer que <strong>TypeScript permite bivariance em arrays e propriedades por design prático, mas isso requer sua vigilância</strong>. Na prática, compreender quando aplicar cada conceito ajuda a escrever código type-safe: use tipos de retorno para aproveitar covariance, parâmetros para contravariance, e evite casts perigosos quando possível.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.typescriptlang.org/docs/handbook/type-compatibility.html#function-parameter-bivariance" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Type Variance</a></li>

<li><a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" target="_blank" rel="noopener noreferrer">Advanced TypeScript - Generics and Constraints</a></li>

<li><a href="https://egghead.io/courses/advanced-typescript" target="_blank" rel="noopener noreferrer">egghead.io - Understanding TypeScript&#039;s type system</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Covariance_and_contravariance" target="_blank" rel="noopener noreferrer">Mdn Web Docs - Covariance and Contravariance</a></li>

<li><a href="https://effectivetypescript.com/" target="_blank" rel="noopener noreferrer">Effective TypeScript - Items 26-28 sobre Variance</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Boas Práticas de Mocks com TypeScript: jest-mock-extended e Tipagem de Dependências para Times Ágeis
Boas Práticas de Mocks com TypeScript: jest-mock-extended e Tipagem de Dependências para Times Ágeis

O Problema: Por Que Mocks São Essenciais em Testes Quando você escreve testes...

Publicando Bibliotecas TypeScript: Types, Exports e Compatibilidade na Prática
Publicando Bibliotecas TypeScript: Types, Exports e Compatibilidade na Prática

Preparando seu Projeto TypeScript para Publicação Antes de publicar uma bibli...

tsconfig.json Avançado: Todas as Opções que Importam na Prática: Do Básico ao Avançado
tsconfig.json Avançado: Todas as Opções que Importam na Prática: Do Básico ao Avançado

Entendendo o tsconfig.json: Fundamentos Práticos O é o arquivo de configuraçã...