TypeScript vs JavaScript em 2026: quando os tipos compensam
Equipe Arvucore
September 22, 2025 · Atualizado em August 26, 2026
13 min read
Use TypeScript quando o código vai sobreviver a quem o escreveu: projetos de vários meses, equipes com mais de duas pessoas, APIs públicas ou internas e qualquer coisa que vai passar por refatoração. Use JavaScript puro quando o código é pequeno, de vida curta ou pertence a uma única pessoa que vai apagá-lo antes de precisar de manutenção. Em 2026, o argumento das ferramentas de build contra o TypeScript praticamente sumiu; o que sobra é um julgamento sobre quanto um contrato em tempo de compilação vale para a sua equipe.
O que o TypeScript realmente garante (e o que não garante)
O TypeScript é um verificador estático de tipos em cima do JavaScript. Ele lê o código, infere ou confere os tipos, aponta as incompatibilidades e depois apaga cada anotação. O JavaScript que roda é o mesmo que você teria escrito à mão. Nada muda em runtime.
Isso dá três garantias concretas em tempo de compilação:
- Toda propriedade que você acessa existe no tipo declarado.
- Toda função é chamada com os formatos de argumento que ela declara.
- Todo valor marcado como possivelmente
nullouundefinedé tratado antes do uso (comstrictNullChecksligado).
E três coisas que ele não garante:
- Que os dados que chegam em runtime são o que você disse que eram. Um
as Userna resposta de umfetché uma promessa ao compilador, não uma verificação. - Que a sua lógica está correta. Uma função tipada como
(a: number, b: number) => numberainda pode devolver o número errado. - Que as tipagens de terceiros são precisas. Pacotes
@types/*e arquivos.d.tsescritos à mão são documentação em que o compilador confia.
O TypeScript também é deliberadamente "unsound". any, asserções de tipo e // @ts-ignore são válvulas de escape por design. Uma base cheia deles tem uma etapa de build e nenhuma garantia. O valor do TypeScript é proporcional ao rigor que você está disposto a aplicar.
Custo e benefício por tamanho de projeto e de equipe
A balança muda com a escala. O custo do TypeScript é mais ou menos constante: um arquivo de configuração, uma etapa de verificação de tipos, alguma curva de aprendizado e, de vez em quando, tempo perdido com um generic complicado. O benefício cresce com o número de pontos de chamada, de pessoas e de anos.
Desenvolvedor solo, código de vida curta. Um script que lê um CSV e gera um relatório ganha quase nada com tipos. Você tem o programa inteiro na cabeça; uma execução que falha diz o que está errado mais rápido do que um erro de tipo diria.
Equipe pequena, um produto, alguns meses. Esta é a zona cinzenta. Se o produto sobreviver ao protótipo, você vai sentir falta dos tipos na primeira refatoração grande. A maioria das equipes nessa situação começa com TypeScript em modo não estrito e aperta conforme o código se estabiliza.
Várias equipes, módulos compartilhados, anos de manutenção. Aqui os tipos não são opcionais. São o único mecanismo que escala para avisar um desenvolvedor de uma equipe que uma mudança no módulo de outra equipe quebrou o ponto de chamada dele, antes de o código chegar ao CI. Renomeie um campo em um tipo compartilhado e o compilador lista cada arquivo afetado em segundos. Em JavaScript puro, o mesmo rename é um grep e uma reza.
A outra dimensão é a rotatividade. Tipos são a documentação mais barata que existe, porque o compilador os mantém honestos. Um engenheiro novo lê a assinatura de uma função e sabe o que entra e o que sai sem abrir outros três arquivos nem ler os testes. Combinado com um bom processo de code review, isso encurta a rampa de semanas para dias em uma base grande.
TypeScript vs JavaScript: tabela comparativa
| Critério | JavaScript | TypeScript |
|---|---|---|
| Etapa de build | Nenhuma obrigatória (ESM roda nativamente) | Type stripping ou transpilação; quase instantânea com esbuild/SWC/Vite ou o stripping nativo do Node |
| Segurança na refatoração | Depende de testes e grep | O compilador aponta cada ponto de chamada afetado |
| Onboarding | Ler testes e chamadas para entender os contratos | Assinaturas e interfaces documentam os contratos |
| Suporte da IDE | Inferência pelo uso; muitas vezes chuta | Autocomplete preciso, ir para definição, erros inline |
| Segurança em runtime | Nenhuma vinda da linguagem | Nenhuma vinda da linguagem; exige validação nas fronteiras |
| Tipagens de bibliotecas | Não se aplica | A maioria dos pacotes grandes já traz tipos; alguns atrasam ou faltam |
| Curva de aprendizado | Menor; uma linguagem só | JavaScript mais um sistema de tipos; generics e utility types levam tempo |
| Superfície de configuração | Mínima | tsconfig.json, flags strict, configurações de módulo |
| Pegar "undefined is not a function" | Em runtime | Em tempo de compilação, se o valor veio de código tipado |
A linha que surpreende as pessoas é segurança em runtime: as duas colunas dizem "nenhuma". É exatamente o ponto da próxima seção.
O contexto de ferramentas em 2026: o argumento da etapa de build acabou
Durante anos, o argumento mais forte contra o TypeScript era o atrito com as ferramentas. Esse argumento praticamente desabou.
- O Node executa arquivos TypeScript diretamente. As versões atuais do Node removem as anotações de tipo no carregamento, então
node app.tsfunciona em código que usa apenas sintaxe apagável (semenum, sem parameter properties, semnamespacelegado com código em runtime). Confira a documentação do Node.js para as regras exatas da sua versão. - esbuild e SWC transpilam sem verificar tipos. Removem os tipos em milissegundos. O Vite usa esbuild em desenvolvimento e pode usar bundling baseado em Rolldown em produção, então um frontend em TypeScript builda tão rápido quanto um em JavaScript. Se você está escolhendo um bundler, veja nosso comparativo de ferramentas de build para frontend.
tscé um verificador de tipos, não o seu bundler. A configuração moderna rodatsc --noEmitno editor (via language server) e no CI, e deixa o bundler ou o runtime cuidar da saída JavaScript de fato. Verificar tipos e transpilar são preocupações separadas, com ferramentas separadas.- Um compilador nativo está a caminho. A Microsoft vem portando o compilador e o language server do TypeScript para Go, com o objetivo declarado de acelerar muito a verificação de tipos e a resposta do editor. Trate isso como direção, não como uma versão para planejar em cima hoje; a semântica da linguagem não muda.
- Bun e Deno executam TypeScript nativamente há anos.
Consequência prática: a pergunta "o TypeScript vai deixar meu build lento?" virou "a verificação de tipos vai deixar meu CI lento?". Em um monorepo grande a resposta ainda pode ser sim, e as soluções são project references, builds incremental e rodar a verificação em paralelo com os testes.
Um tsconfig.json mínimo de 2026 para um serviço Node fica assim:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true
},
"include": ["src"]
}
erasableSyntaxOnly rejeita o punhado de recursos do TypeScript que exigem transpilação de verdade, o que mantém o código compatível com o type stripping do Node.
Validação em runtime nas fronteiras: por que tipos sozinhos não bastam
Todo programa não trivial tem fronteiras por onde entram dados sem tipo: corpo de requisições HTTP, respostas de APIs de terceiros, linhas do banco, filas de mensagens, variáveis de ambiente, arquivos em disco. O TypeScript não enxerga além dessas fronteiras. O que você anota ali é uma suposição.
// Isto compila. Também é uma mentira se a API mudar.
const user = (await res.json()) as User;
user.email.toLowerCase(); // TypeError em runtime se email não vier
A solução é validar na fronteira e derivar o tipo estático do validador, para que os dois não se descolem. Tanto o zod quanto o valibot suportam esse padrão; o valibot é tree-shakeable e menor para bundles de browser.
import { z } from "zod";
const User = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum(["admin", "member"]),
});
type User = z.infer<typeof User>;
const user = User.parse(await res.json()); // lança erro com mensagem precisa
user.email.toLowerCase(); // agora garantido como string
Regras práticas:
- Valide uma vez, na borda. Dentro do sistema, confie nos tipos.
- Derive tipos dos schemas, nunca o contrário.
- Trate variáveis de ambiente como fronteira. Um schema para
process.envpega um deploy mal configurado na inicialização, e não às 3 da manhã. - Combine com uma estratégia consistente de tratamento de erros e logging para que as falhas de validação fiquem visíveis, não engolidas.
Equipes que pulam essa etapa ficam com o pior dos dois mundos: a cerimônia dos tipos e as falhas em runtime do código sem tipo.
Caminho de migração para uma base JavaScript existente
Você não precisa de uma reescrita. O compilador foi projetado para adoção gradual, e uma base mista JS/TS é um estado normal e estável, não uma transição para atravessar correndo.
Etapa 1: verificar o JavaScript como está.
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false
},
"include": ["src"]
}
Rode npx tsc. Você vai receber erros em arquivos .js só pela inferência: propriedades com erro de digitação, funções chamadas com o número errado de argumentos. Corrija ou suprima com // @ts-expect-error (que falha se o erro desaparecer, ao contrário do @ts-ignore).
Etapa 2: adicionar tipos JSDoc nos arquivos mais editados. Sem renomear nada, sem mudar o build. O compilador lê JSDoc.
/**
* @param {{ price: number, qty: number }[]} items
* @returns {number}
*/
export function total(items) {
return items.reduce((s, x) => s + x.price * x.qty, 0);
}
Algumas equipes param aqui para sempre. Bibliotecas JavaScript grandes publicam tipos desse jeito e nunca renomeiam um arquivo.
Etapa 3: renomear módulos para .ts, começando pelas folhas. Comece pelos utilitários e modelos de dados com poucos imports e avance em direção aos pontos de entrada. Cada arquivo renomeado recebe anotações de verdade. Adicione pacotes @types/* para as dependências conforme aparecerem; escreva um .d.ts local para as que não têm tipagem.
Etapa 4: ligar as flags strict em ordem. Ativar strict de uma vez em uma base grande gera milhares de erros e trava o esforço. Ligue uma por vez e corrija cada lote:
noImplicitAnystrictNullChecks(a que tem mais bugs reais por trás)strictFunctionTypes,strictPropertyInitializationnoUncheckedIndexedAccess(opcional, rigorosa com indexação de arrays e objetos)
Etapa 5: impor no CI. tsc --noEmit vira uma verificação obrigatória. Proíba novos any com @typescript-eslint/no-explicit-any. Acompanhe a contagem de comentários @ts-expect-error e faça ela cair.
Mantenha algumas regras o tempo todo:
- Nunca misture migração de tipos com mudança de comportamento no mesmo pull request.
- Converta os testes junto com os módulos que eles cobrem.
- Se um arquivo brigar com você por mais de uma hora, deixe-o em JavaScript com JSDoc e siga em frente.
É a mesma lógica incremental que vale para migrar sistemas legados: passos pequenos e reversíveis, cada um entregável.
Antes e depois: o que um tipo pega de fato
JavaScript puro:
function applyDiscount(order, discount) {
return order.total - order.total * discount.percent;
}
applyDiscount({ total: 100 }, { percentage: 10 });
// Retorna NaN. Ninguém percebe até uma fatura mostrar "NaN €".
TypeScript:
type Order = { total: number };
type Discount = { percent: number }; // 0.1 para 10%
function applyDiscount(order: Order, discount: Discount): number {
return order.total - order.total * discount.percent;
}
applyDiscount({ total: 100 }, { percentage: 10 });
// Error: Object literal may only specify known properties,
// and 'percentage' does not exist in type 'Discount'.
Repare no que o tipo não pegou: o comentário diz que percent é uma fração, mas quem passa 10 em vez de 0.1 ainda compila. Isso é um contrato de lógica, e o lugar dele é um teste ou um branded type, não um number simples. Tipos estreitam o espaço de bugs; não o esvaziam. É aqui que uma abordagem disciplinada de desenvolvimento orientado a testes preenche a lacuna.
Quando JavaScript puro é a escolha certa
TypeScript é o padrão, não a lei. JavaScript puro é adequado, e às vezes melhor, quando:
- O código tem menos de algumas centenas de linhas e um único dono.
- É um script de build, uma migration, uma correção pontual de dados ou um helper de CI.
- É um protótipo que você se comprometeu a descartar (e vai descartar mesmo).
- O runtime proíbe etapa de build e você não pode usar o type stripping do Node.
- O código é pesado em metaprogramação dinâmica (proxies, estruturas geradas em runtime), onde os tipos seriam basicamente
anyde qualquer forma. - A equipe não tem nenhuma experiência com TypeScript e o projeto termina antes de a curva de aprendizado se pagar.
Mesmo nesses casos, um // @ts-check no topo de um arquivo .js dá verificação por inferência no editor de graça. Não custa nada tentar.
Checklist de decisão
Responda antes de escolher. Três ou mais "sim" apontam para TypeScript.
- Esse código ainda vai estar rodando daqui a doze meses?
- Mais de duas pessoas vão editá-lo?
- Ele expõe funções ou tipos dos quais outros módulos ou serviços dependem?
- Você espera renomear ou remodelar os modelos de dados centrais mais de uma vez?
- Você faz onboarding de engenheiros com frequência?
- O modelo de domínio não é trivial (mais do que um punhado de tipos de entidade)?
- Você já tem validação em runtime nas fronteiras, ou está disposto a adicionar?
Sinais que apontam para JavaScript:
- Um dono, um propósito, descartável.
- Não ter etapa de build é aceitável e o runtime não consegue remover tipos.
- O projeto tem prazo contado em dias e ninguém na equipe conhece TypeScript.
Se você está escolhendo a stack inteira, não só a linguagem, pese as decisões ao redor (runtime, framework, hospedagem) com as mesmas perguntas.
Recomendação
Adote TypeScript com strict: true como padrão em qualquer base que vai receber manutenção. Deixe o bundler ou o runtime remover os tipos e rode tsc --noEmit como verificação separada no editor e no CI. Valide toda entrada externa com uma biblioteca de schemas e derive seus tipos dos schemas. Mantenha any e asserções de tipo fora da base, exceto atrás de um comentário que explique o motivo.
Em uma base JavaScript existente, ligue checkJs, adicione JSDoc nos arquivos que mais mudam e renomeie os módulos das folhas para dentro enquanto ativa as flags strict uma por vez. Não agende uma reescrita; agende um trimestre de pull requests pequenos.
Reserve JavaScript puro para scripts, protótipos e ferramentas com um dono e vida curta. Na Arvucore, costumamos recomendar TypeScript desde o primeiro commit nos projetos de clientes, porque o custo de adicioná-lo depois é sempre maior do que o de começar com ele.
Pronto para Transformar seu Negócio?
Vamos conversar sobre como nossas soluções podem ajudá-lo a alcançar seus objetivos. Entre em contato com nossos especialistas hoje mesmo.
Falar com um EspecialistaTags:
Equipe Arvucore
A equipe editorial da Arvucore é formada por profissionais experientes em desenvolvimento de software. Somos dedicados a produzir e manter conteúdo de alta qualidade que reflete as melhores práticas da indústria e insights confiáveis.
Perguntas frequentes
- Devo usar TypeScript ou JavaScript em um projeto novo em 2026?
- Use TypeScript em tudo que vai durar mais do que alguns meses, envolver mais de duas pessoas ou expor uma API da qual outro código depende. JavaScript puro serve bem para scripts, protótipos e ferramentas pequenas de propósito único.
- TypeScript evita erros em runtime?
- Só em parte. O TypeScript verifica o código em tempo de compilação e apaga os tipos antes da execução. Dados que entram em runtime (requisições HTTP, arquivos JSON, linhas do banco, variáveis de ambiente) não são verificados, a menos que você os valide com uma biblioteca como zod ou valibot.
- O build com TypeScript é mais lento do que com JavaScript?
- Transpilar hoje custa quase nada: esbuild, SWC, Vite e o type stripping nativo do Node removem os tipos em milissegundos. O custo que sobra é a verificação de tipos com o tsc, que roda como etapa separada, geralmente no editor e no CI.
- Dá para migrar uma base JavaScript existente para TypeScript aos poucos?
- Sim. Ative allowJs e checkJs, adicione tipos JSDoc nos arquivos mais editados, renomeie os arquivos para .ts um módulo por vez e ligue as flags strict em etapas. A maioria das equipes mantém uma base mista por meses sem travar as entregas.
- Ainda preciso de testes se uso TypeScript?
- Sim. Os tipos provam que as estruturas batem; os testes provam que o comportamento está correto. O TypeScript reduz uma categoria de bugs (propriedade errada, argumento errado, acesso a null), mas não diz nada sobre a lógica de negócio.
- Qual é a principal desvantagem do TypeScript?
- Atrito: uma etapa de compilação, uma superfície de configuração, brigas ocasionais com generics complexos e tipagens de bibliotecas que atrasam em relação aos lançamentos. Em código pequeno e de vida curta, esse atrito pode pesar mais do que o benefício.
Artigos relacionados

Vite vs Webpack vs Parcel em 2026: qual escolher
Vite, Webpack 5 e Parcel 2 comparados em dev server, HMR, config, plugins, suporte legado e monorepos, com um roteiro de migração de Webpack para Vite.

WebAssembly em 2026: desempenho, casos de uso e limites
Quando o WebAssembly supera o JavaScript, quando não supera, de que linguagens compilar e um checklist para decidir se sua carga de trabalho precisa dele.

Acessibilidade (A11y) no Desenvolvimento Web: Diretrizes WCAG 2.1
Como guia da Arvucore, este artigo explica a Acessibilidade (A11y) no desenvolvimento web e as diretrizes WCAG 2.1, oferecendo conselhos práticos para tomadores de decisão europeus e equipes técnicas. Destaca como o desenvolvimento de acessibilidade web melhora a experiência do usuário, conformidade legal e alcance de mercado, incluindo considerações de design que integram acessibilidade desde o início dos ciclos de vida do produto.

PWA Desenvolvimento para Empresas Europeias
Na Arvucore, ajudamos empresas europeias a adotar aplicativos web progressivos para melhorar o desempenho, o alcance e o engajamento do usuário. Este artigo explora o desenvolvimento de PWAs sob medida para empresas europeias, abordando vantagens comerciais, padrões técnicos para aplicativos web offline, benchmarks de conformidade e desempenho e seleção de fornecedores. Tomadores de decisão e líderes técnicos encontrarão orientações práticas para integrar PWAs em estratégias de produtos e compras.