Marcos N
← Voltar ao blog

Front-end

TypeScript vs JavaScript: qual escolher para seu projeto?

Entenda as diferenças entre TypeScript e JavaScript, vantagens, desvantagens, performance, segurança, React, Next.js e quando usar cada linguagem em projetos reais.

Marcos N24 min de leitura
TypeScript vs JavaScript: qual escolher para seu projeto?

Se você trabalha com Front-end, cedo ou tarde aparece a pergunta:

Vale a pena usar TypeScript ou JavaScript já é suficiente?

À primeira vista, a comparação parece simples.

JavaScript é a linguagem que roda no navegador. TypeScript adiciona tipos.

Mas essa explicação é pequena demais para uma decisão que afeta diretamente:

  • velocidade de desenvolvimento;
  • manutenção;
  • refatoração;
  • experiência de desenvolvimento;
  • qualidade do código;
  • onboarding de novos desenvolvedores;
  • integração com APIs;
  • segurança durante mudanças;
  • tamanho e longevidade do projeto.

A própria documentação do TypeScript define a linguagem como uma camada sobre JavaScript, adicionando um sistema de tipos que analisa o programa antes da execução. Em outras palavras: TypeScript não substitui JavaScript no runtime; ele tenta encontrar problemas antes que o JavaScript seja executado.

Neste artigo vamos comparar os dois sem transformar a discussão em uma disputa de "qual linguagem é melhor".

A pergunta mais útil é:

Qual delas faz mais sentido para o problema, equipe e projeto que estou construindo?

O que vamos entender

  • O que realmente diferencia JavaScript de TypeScript.
  • Tipagem dinâmica vs análise estática.
  • O que TypeScript consegue detectar antes da execução.
  • O que TypeScript não consegue proteger.
  • Performance e tamanho do bundle.
  • React e Next.js com JavaScript e TypeScript.
  • APIs e dados externos.
  • any, unknown, interfaces e unions.
  • Custo de adoção e manutenção.
  • Quando JavaScript continua sendo uma excelente escolha.
  • Quando TypeScript começa a se pagar.
  • Como migrar um projeto JavaScript sem reescrever tudo.

Notas:

TypeScript e JavaScript não são mundos separados. Código JavaScript válido pode fazer parte de um projeto TypeScript, e o TypeScript continua dependendo do comportamento do JavaScript em runtime.

Tecnologias Utilizadas

Neste artigo vamos utilizar:

  • JavaScript
  • TypeScript
  • React
  • Next.js
  • Node.js
  • conceitos de tipagem, inferência, narrowing e contratos de dados.

Antes de comparar: o que é JavaScript?

JavaScript é uma linguagem de programação com tipagem dinâmica.

Isso significa que uma variável não fica permanentemente associada a um único tipo.

index.js
let value = 10;

value = "Marcos";

value = true;

O mesmo identificador recebeu:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

e isso é válido em JavaScript.

Essa flexibilidade é parte importante da linguagem.

Ela permite começar rápido, experimentar ideias e construir aplicações com pouca cerimônia.

Mas também significa que determinados erros só aparecem quando o código é executado.

Veja:

index.js
function formatUser(user) {
  return user.name.toUpperCase();
}

formatUser({
  id: 1,
});

O código é JavaScript válido.

Porém, em runtime:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

O JavaScript não analisa antecipadamente se name existe naquele objeto.

Ele executa.


E o que é TypeScript?

TypeScript adiciona um sistema de tipos sobre JavaScript.

Podemos reescrever o exemplo:

user.ts
type User = {
  id: number;
  name: string;
};

function formatUser(user: User) {
  return user.name.toUpperCase();
}

formatUser({
  id: 1,
});

Agora o editor e o compilador conseguem identificar que o objeto não satisfaz o contrato User.

Antes mesmo de executar a aplicação.

A ideia pode ser representada assim:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Enquanto TypeScript adiciona outra etapa:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Essa etapa adicional muda bastante a experiência de trabalhar em projetos maiores.


JavaScript vs TypeScript em uma frase

Uma forma simples de pensar:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar
Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Isso não significa que TypeScript elimina bugs.

Ele elimina ou reduz determinadas categorias de bugs.


JavaScript continua existindo por baixo do TypeScript

Esse ponto é fundamental.

Considere:

index.ts
const price: number = 100;

console.log(price);

O navegador não executa a anotação:

: number

Depois da transformação, o código equivalente é JavaScript:

index.js
const price = 100;

console.log(price);

Os tipos existem durante o desenvolvimento.

Eles não permanecem como uma camada mágica de proteção no navegador.

Por isso aprender TypeScript sem entender JavaScript costuma gerar uma falsa sensação de segurança.

Notas:

TypeScript não muda as regras de execução do JavaScript. Promises, closures, coerção de tipos, event loop, objetos, arrays e comportamento do runtime continuam sendo JavaScript.


A principal diferença: quando o erro aparece?

Imagine uma função responsável por calcular desconto.

JavaScript

discount.js
function calculateDiscount(price, discount) {
  return price - price * discount;
}

calculateDiscount("100", "abc");

Nada impede a chamada.

O resultado pode acabar sendo:

NaN

dependendo dos valores envolvidos.

TypeScript

discount.ts
function calculateDiscount(
  price: number,
  discount: number
): number {
  return price - price * discount;
}

calculateDiscount("100", "abc");

O TypeScript consegue avisar que estamos enviando string onde a função espera number.

A diferença não é:

TypeScript sabe programar melhor.

É:

TypeScript possui mais informação para analisar a intenção do código.


TypeScript funciona como documentação executável?

Em parte, sim.

Considere esta função JavaScript:

user.js
function updateUser(user, options) {
  // ...
}

Se você acabou de entrar no projeto, algumas perguntas surgem:

O que existe em user?

options é obrigatório?

Qual propriedade posso enviar?

A função retorna alguma coisa?

Podemos descobrir lendo:

  • implementação;
  • documentação;
  • testes;
  • chamadas existentes.

Com TypeScript:

user.ts
interface User {
  id: string;
  name: string;
  email: string;
}

type UpdateUserOptions = {
  sendNotification?: boolean;
  source: "profile" | "admin";
};

function updateUser(
  user: User,
  options: UpdateUserOptions
): Promise<User> {
  // ...
}

O contrato já responde muita coisa.

O editor consegue inclusive sugerir:

sendNotification
source

durante a escrita.

Essa é uma das vantagens mais importantes do TypeScript em equipes.

Não é apenas "evitar erro".

É reduzir o número de coisas que o desenvolvedor precisa guardar mentalmente.


IntelliSense e autocomplete: uma diferença maior do que parece

Considere:

interface Product {
  id: number;
  name: string;
  price: number;
  available: boolean;
}

const product: Product = {
  id: 1,
  name: "Teclado",
  price: 299,
  available: true,
};

Ao digitar:

product.

o editor já conhece:

id
name
price
available

Agora imagine isso em:

  • centenas de componentes;
  • serviços;
  • hooks;
  • endpoints;
  • DTOs;
  • formulários;
  • estados.

A experiência de desenvolvimento muda bastante.

Especialmente quando você não escreveu originalmente aquele código.


Inferência de tipos: TypeScript não significa tipar tudo

Um erro comum é imaginar TypeScript assim:

const name: string = "Marcos";
const age: number = 29;
const active: boolean = true;

Isso é válido.

Mas muitas dessas anotações são redundantes.

TypeScript consegue inferir:

index.ts
const name = "Marcos";
const age = 29;
const active = true;

O editor já entende os tipos.

Essa capacidade é chamada de type inference.

Na prática, um bom projeto TypeScript não precisa parecer:

tipo
tipo
tipo
tipo
tipo

em todas as linhas.

Você adiciona contratos principalmente onde eles ajudam a definir fronteiras e intenções.


Narrowing: TypeScript entende o fluxo do JavaScript

Veja:

formatter.ts
function formatValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  }

  return value.toFixed(2);
}

Antes do if:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Dentro da condição:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Depois:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

O TypeScript utiliza o próprio fluxo de controle do JavaScript para refinar os tipos.

Isso é chamado de narrowing.

E é um dos motivos pelos quais TypeScript consegue adicionar segurança sem exigir que você abandone a maneira natural de escrever JavaScript.


Um exemplo mais próximo de Front-end

Imagine um componente que pode representar diferentes estados.

Uma abordagem comum em JavaScript:

Status.js
function Status({ status }) {
  if (status === "loading") {
    return "Carregando...";
  }

  if (status === "success") {
    return "Concluído!";
  }

  return "Erro";
}

Nada impede:

<Status status="sucess" />

Perceba o erro:

sucess

em vez de:

success

TypeScript permite restringir:

type Status =
  | "loading"
  | "success"
  | "error";

Então:

Status.tsx
type StatusProps = {
  status: "loading" | "success" | "error";
};

export function Status({ status }: StatusProps) {
  // ...
}

Agora o erro de digitação pode ser identificado durante o desenvolvimento.


React com JavaScript ou TypeScript?

React funciona muito bem com ambos.

Não existe obrigação técnica de usar TypeScript.

Um componente JavaScript pode ser perfeitamente válido:

Button.jsx
export function Button({
  label,
  disabled,
  onClick,
}) {
  return (
    <button
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

Em TypeScript:

Button.tsx
type ButtonProps = {
  label: string;
  disabled?: boolean;
  onClick: () => void;
};

export function Button({
  label,
  disabled = false,
  onClick,
}: ButtonProps) {
  return (
    <button
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

O segundo componente explicita seu contrato.

Quem for utilizá-lo sabe que:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Em um Design System com dezenas de componentes, essa diferença fica muito relevante.


Props complexas mostram melhor o valor

Imagine um modal:

Modal.tsx
type ModalProps = {
  title: string;
  open: boolean;
  size?: "sm" | "md" | "lg";
  onClose: () => void;
  actions?: Array<{
    label: string;
    variant: "primary" | "secondary" | "danger";
    onClick: () => void;
  }>;
};

O próprio tipo passa a comunicar a API do componente.

Isso melhora:

  • autocomplete;
  • manutenção;
  • reutilização;
  • consistência;
  • refatorações.

Em bibliotecas internas de UI isso faz bastante diferença.


TypeScript ajuda muito em refatorações

Imagine que temos:

interface User {
  id: string;
  fullName: string;
}

Depois decidimos mudar:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Em JavaScript, precisamos procurar manualmente todos os lugares que dependem dessa propriedade.

Em TypeScript, ao alterar:

interface User {
  id: string;
  name: string;
}

o compilador começa a apontar vários pontos impactados.

Isso transforma o type checker em uma espécie de mapa da refatoração.

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Em projetos grandes, esse talvez seja um dos maiores ganhos.


APIs: aqui aparece uma limitação importante

Agora chegamos a um ponto que muita gente interpreta errado.

Imagine:

type User = {
  id: number;
  name: string;
};

const response = await fetch("/api/user");
const user: User = await response.json();

Parece seguro.

Mas não necessariamente é.

O servidor pode devolver:

{
  "id": "abc",
  "username": "Marcos"
}

TypeScript não está validando automaticamente os dados recebidos em runtime.

Ele confia na informação que você forneceu ao código.

Isso significa:

TypeScript
≠
validação de dados externos

Tipo estático não substitui validação em runtime

Para dados externos, precisamos validar.

Podemos fazer manualmente:

user.ts
type User = {
  id: number;
  name: string;
};

function isUser(value: unknown): value is User {
  if (
    typeof value !== "object" ||
    value === null
  ) {
    return false;
  }

  return (
    "id" in value &&
    typeof value.id === "number" &&
    "name" in value &&
    typeof value.name === "string"
  );
}

Então:

const data: unknown = await response.json();

if (!isUser(data)) {
  throw new Error("Resposta inválida");
}

console.log(data.name);

Também existem bibliotecas específicas para schemas e validação.

A lição é:

TypeScript protege o código que ele consegue analisar. Não transforma automaticamente uma API externa em uma fonte confiável.


any: como transformar TypeScript em JavaScript sem perceber

Considere:

function processUser(user: any) {
  user.name.toUpperCase();
  user.address.city.toLowerCase();
  user.whatever.foo.bar();
}

O TypeScript praticamente deixa de ajudar.

any diz:

Confie em mim. Eu sei o que estou fazendo.

Às vezes isso é necessário.

Mas espalhar any pelo projeto elimina boa parte do benefício do TypeScript.

Quando não sabemos o tipo de um valor, muitas vezes é melhor:

unknown

Veja:

function processUser(user: unknown) {
  // precisamos validar antes
}

unknown representa:

Eu realmente não sei o que isso é ainda.

É uma diferença pequena na sintaxe, mas grande na segurança.


JavaScript é mais rápido que TypeScript?

Essa pergunta precisa ser dividida em duas.

Tempo de desenvolvimento

TypeScript adiciona trabalho:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Em projetos pequenos, isso pode representar mais tempo inicialmente.

Performance em runtime

Normalmente, comparar "performance do TypeScript vs JavaScript" dessa forma não faz muito sentido.

O TypeScript é transformado em JavaScript antes da execução.

Ou seja, o navegador executa JavaScript.

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

As anotações de tipos são removidas.

TypeScript não torna automaticamente seu código mais rápido nem mais lento no navegador simplesmente por possuir tipos.

Notas:

A principal diferença de custo está no desenvolvimento, build, tooling e complexidade do código produzido — não em o navegador "executar TypeScript".


TypeScript aumenta o bundle?

As anotações de tipos:

const price: number = 100;

não precisam ir para o navegador.

O resultado é equivalente a:

const price = 100;

Então adicionar:

: number

não significa aumentar diretamente o JavaScript entregue ao usuário.

O bundle final depende muito mais de:

  • dependências;
  • arquitetura;
  • tree shaking;
  • código importado;
  • polyfills;
  • framework;
  • estratégia de build.

Onde JavaScript ainda é uma excelente escolha?

Depois de tantos benefícios do TypeScript, seria fácil concluir:

Então JavaScript não faz mais sentido.

Isso seria uma conclusão ruim.

JavaScript continua sendo excelente em vários cenários.

Protótipos

Você quer validar uma ideia rapidamente.

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Quanto menos configuração, melhor.

Scripts pequenos

Por exemplo:

rename-files.js
const files = [
  "image-1.png",
  "image-2.png",
];

for (const file of files) {
  console.log(file);
}

Adicionar toda uma estrutura TypeScript talvez não gere benefício real.

Aprendizado

Se você está aprendendo desenvolvimento web, conhecer JavaScript profundamente continua sendo essencial.

TypeScript não substitui conceitos como:

  • closures;
  • event loop;
  • promises;
  • prototypes;
  • coerção;
  • escopo;
  • módulos;
  • objetos;
  • arrays.

Projetos pequenos e descartáveis

Uma landing page simples com poucas interações talvez não precise do mesmo nível de estrutura de um SaaS mantido por cinco anos.


Quando TypeScript começa a fazer muito sentido?

Eu começaria a considerar TypeScript fortemente quando aparecem alguns sinais.

Muitas entidades de domínio

User
Product
Order
Invoice
Subscription
Permission
Company

Muitas APIs

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Muitos desenvolvedores

Quanto mais pessoas alteram o mesmo código, mais importante fica comunicar contratos.

Projeto de longa duração

Código que ficará anos em produção será refatorado muitas vezes.

Design System

Componentes reutilizáveis se beneficiam bastante de APIs de props bem definidas.

Regras de negócio complexas

Unions e tipos discriminados conseguem representar estados de forma muito poderosa.


Um exemplo: representando estados corretamente

Imagine um processo de carregamento.

Uma implementação comum:

type State = {
  loading: boolean;
  error: string | null;
  data: User | null;
};

O problema é que conseguimos representar estados estranhos:

loading = true
error = "Falhou"
data = usuário

Tudo ao mesmo tempo.

Podemos modelar melhor:

state.ts
type UserState =
  | {
      status: "loading";
    }
  | {
      status: "error";
      message: string;
    }
  | {
      status: "success";
      data: User;
    };

Agora cada estado possui apenas os dados que fazem sentido.

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Isso é mais do que autocomplete.

É usar tipos para representar regras do domínio.


JavaScript permite escrever código ruim. TypeScript também.

Outro ponto importante:

TypeScript não corrige arquitetura.

Você pode escrever:

function doEverything(data: any): any {
  // 800 linhas
}

e continuar com:

  • responsabilidades misturadas;
  • componentes gigantes;
  • acoplamento;
  • duplicação;
  • nomes ruins;
  • testes inexistentes.

TypeScript ajuda em uma dimensão específica:

tipos e contratos

Qualidade de software ainda depende de:

  • arquitetura;
  • testes;
  • revisão;
  • observabilidade;
  • boas abstrações;
  • decisões de produto.

Tipagem demais também pode virar problema

Existe o outro extremo.

Você encontra código como:

type DeepReadonlyRecursiveMappedEntity<
  T extends Record<string, unknown>,
  K extends keyof T = keyof T
> = {
  // ...
};

para resolver um problema que talvez pudesse ser:

type User = {
  name: string;
};

O sistema de tipos do TypeScript é poderoso.

Isso não significa que precisamos utilizar toda a capacidade disponível.

Uma boa regra:

Tipos devem reduzir a complexidade mental do código, não aumentar.

Se um tipo leva 20 minutos para ser entendido e protege algo irrelevante, talvez a abstração esteja trabalhando contra a equipe.


JavaScript vs TypeScript em projetos React

Podemos resumir um cenário típico.

JavaScript

Vantagens

  • início rápido;
  • menos configuração mental;
  • sintaxe mais enxuta;
  • excelente para protótipos;
  • menor barreira para iniciantes.

Desvantagens

  • contratos ficam implícitos;
  • refatorações maiores exigem mais cuidado;
  • alguns erros aparecem só em runtime;
  • componentes grandes podem ficar difíceis de navegar;
  • autocomplete pode ter menos contexto.

TypeScript

Vantagens

  • contratos explícitos;
  • autocomplete mais poderoso;
  • refatorações mais seguras;
  • integração forte com IDE;
  • unions e tipos ajudam a modelar estados;
  • melhora comunicação em equipes.

Desvantagens

  • curva de aprendizado;
  • configuração;
  • erros de tipos podem parecer confusos;
  • é possível criar abstrações exageradas;
  • bibliotecas mal tipadas podem gerar trabalho adicional.

E no Next.js?

Next.js suporta TypeScript naturalmente.

Em projetos que possuem:

  • Server Components;
  • Client Components;
  • APIs;
  • parâmetros de rota;
  • formulários;
  • bancos;
  • autenticação;
  • schemas;
  • componentes compartilhados;

TypeScript pode se tornar especialmente útil porque os dados atravessam muitas fronteiras.

Por exemplo:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Cada transição é um lugar potencial para inconsistência.

Tipos ajudam a tornar essas fronteiras visíveis.

Mas continuam não substituindo validação em runtime nas entradas do sistema.


Um cenário real: formulário

Imagine:

UserForm.tsx
type UserFormData = {
  name: string;
  email: string;
  age?: number;
};

function saveUser(data: UserFormData) {
  // ...
}

O formulário já comunica sua estrutura.

Mas o usuário pode enviar:

email inválido
idade negativa
nome vazio

Todos podem tecnicamente satisfazer os tipos:

string
number

Por isso precisamos separar:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

TypeScript responde:

email é uma string?

A validação responde:

essa string é realmente um email válido?

São responsabilidades diferentes.


O custo da migração

Imagine um projeto JavaScript com 200 arquivos.

Uma abordagem ruim seria:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

TypeScript pode ser adotado gradualmente.

Passo a Passo

1

Passo 1: Configure TypeScript

Adicione o TypeScript ao projeto sem exigir que todos os arquivos sejam convertidos imediatamente.

2

Passo 2: Comece pelas fronteiras

Tipar APIs, models, serviços e componentes compartilhados costuma gerar benefício rapidamente.

3

Passo 3: Migre arquivos alterados

Ao trabalhar em uma feature existente, aproveite para converter os arquivos relacionados.

4

Passo 4: Reduza any progressivamente

Não bloqueie toda a migração tentando eliminar cada any no primeiro dia.

5

Passo 5: Aumente o rigor

Depois que o projeto estiver estável, evolua as configurações de strictness.


Exemplo de migração simples

Temos:

user.js
export function getUserName(user) {
  return user.name;
}

Podemos começar convertendo:

user.ts
type User = {
  id: string;
  name: string;
};

export function getUserName(
  user: User
): string {
  return user.name;
}

Depois esse User pode virar um contrato compartilhado.

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

A migração começa a gerar valor de maneira incremental.


JavaScript com JSDoc pode ser um meio-termo

Existe ainda outra opção interessante.

Você pode continuar escrevendo JavaScript e adicionar informação de tipos com JSDoc.

user.js
/**
 * @typedef {Object} User
 * @property {string} id
 * @property {string} name
 */

/**
 * @param {User} user
 */
function printUser(user) {
  console.log(user.name);
}

Isso pode melhorar autocomplete e análise sem migrar imediatamente para .ts.

É útil em:

  • bibliotecas JavaScript;
  • scripts maiores;
  • migrações graduais;
  • equipes que ainda não querem adotar TypeScript completamente.

"TypeScript deixa o código mais verboso"

Sim, pode deixar.

Compare:

const user = {
  name: "Marcos",
};

com:

interface User {
  name: string;
}

const user: User = {
  name: "Marcos",
};

Existe mais código.

Mas a comparação correta não é apenas:

quantidade de caracteres

Também precisamos considerar:

Quanto tempo alguém leva para entender?

Quanto custa alterar?

Quanto custa errar?

Quantas pessoas usam esse contrato?

Quanto tempo o projeto vai existir?

Verboso não significa necessariamente ruim.

E conciso não significa necessariamente simples.


"Mas os testes não resolvem isso?"

Testes e TypeScript resolvem problemas diferentes.

TypeScript pode encontrar:

propriedade inexistente
tipo incompatível
argumento incorreto
estado impossível

Testes podem verificar:

regra de negócio
comportamento
integração
fluxo do usuário
resultado esperado

Um projeto saudável não deveria pensar:

TypeScript OU testes

mas:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

Cada camada protege uma parte diferente.


Quando eu escolheria JavaScript?

Eu escolheria JavaScript sem problema para:

Pequenos scripts

automação rápida
CLI pequena
manipulação simples

Estudos iniciais

Para aprender o comportamento real da linguagem.

Protótipos descartáveis

Quando velocidade de experimentação importa mais que manutenção.

Projetos muito simples

Quando o custo adicional de tipos não se paga.

Notas:

Escolher JavaScript conscientemente não significa "escrever código sem qualidade". JavaScript possui módulos, testes, linting, JSDoc, boas práticas e ferramentas maduras.


Quando eu escolheria TypeScript?

Para boa parte das aplicações profissionais que possuem:

  • React ou Next.js;
  • múltiplas páginas;
  • APIs;
  • formulários;
  • regras de negócio;
  • bibliotecas internas;
  • Design System;
  • mais de um desenvolvedor;
  • expectativa de manutenção longa;

eu tenderia a utilizar TypeScript.

Não porque JavaScript não consiga construir esses sistemas.

Consegue.

Mas porque, nesses contextos, os contratos começam a pagar o custo de mantê-los.


TypeScript vs JavaScript: comparação rápida

PontoJavaScriptTypeScript
TipagemDinâmicaAnálise estática sobre JavaScript
Execução no navegadorDiretaNormalmente transformado para JavaScript
Curva inicialMenorMaior
PrototipaçãoExcelenteBoa
AutocompleteBomGeralmente mais rico
ContratosMais implícitosExplícitos
RefatoraçãoMais dependente de testes e buscaType checker ajuda bastante
Dados externosExigem validaçãoTambém exigem validação
RuntimeJavaScriptJavaScript
Projetos grandesFuncionaCostuma trazer benefícios maiores
Scripts pequenosÓtimoPode ser excesso
React / Next.jsExcelente suporteExcelente suporte
Ponto
Tipagem
JavaScript
Dinâmica
TypeScript
Análise estática sobre JavaScript
Ponto
Execução no navegador
JavaScript
Direta
TypeScript
Normalmente transformado para JavaScript
Ponto
Curva inicial
JavaScript
Menor
TypeScript
Maior
Ponto
Prototipação
JavaScript
Excelente
TypeScript
Boa
Ponto
Autocomplete
JavaScript
Bom
TypeScript
Geralmente mais rico
Ponto
Contratos
JavaScript
Mais implícitos
TypeScript
Explícitos
Ponto
Refatoração
JavaScript
Mais dependente de testes e busca
TypeScript
Type checker ajuda bastante
Ponto
Dados externos
JavaScript
Exigem validação
TypeScript
Também exigem validação
Ponto
Runtime
JavaScript
JavaScript
TypeScript
JavaScript
Ponto
Projetos grandes
JavaScript
Funciona
TypeScript
Costuma trazer benefícios maiores
Ponto
Scripts pequenos
JavaScript
Ótimo
TypeScript
Pode ser excesso
Ponto
React / Next.js
JavaScript
Excelente suporte
TypeScript
Excelente suporte

O erro é transformar isso em guerra de linguagem

Discussões como:

JavaScript é bagunça

ou:

TypeScript só atrapalha

não ajudam muito.

Ambas podem estar certas dependendo do contexto.

Uma equipe experiente pode manter um projeto JavaScript excelente.

Uma equipe pode também criar um projeto TypeScript impossível de entender.

Ferramenta não substitui engenharia.


Quando eu usaria cada um em um projeto real?

Landing page simples

JavaScript
✓

Pouca lógica e vida curta.

Experimento de produto

JavaScript
ou
TypeScript leve

Depende da velocidade necessária.

Dashboard administrativo

TypeScript
✓

Muitos dados, filtros, permissões e formulários.

SaaS com React / Next.js

TypeScript
✓

A quantidade de contratos cresce rapidamente.

Design System

TypeScript
✓

A API dos componentes precisa ser clara.

Script interno de 30 linhas

JavaScript
✓

Provavelmente não precisamos complicar.


O que eu considero mais importante nessa escolha?

Para mim, a principal vantagem do TypeScript não é escrever:

name: string

É permitir que o projeto carregue mais informação sobre si mesmo.

Um projeto grande possui milhares de decisões.

Quando os contratos estão apenas na cabeça das pessoas:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

se alguém sai da equipe, parte desse contexto desaparece.

Quando uma parte desse conhecimento está no código:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

ele fica mais acessível.

Essa é uma diferença muito relevante em software mantido por anos.


JavaScript continua sendo obrigatório mesmo se você usa TypeScript

Talvez esse seja o ponto mais importante do artigo.

Aprender TypeScript não elimina a necessidade de aprender JavaScript.

Se você não entende:

closures
promises
event loop
this
objects
arrays
coercion
modules
scope

TypeScript não vai corrigir essa falta de conhecimento.

A própria documentação do TypeScript recomenda que quem está começando programação web entenda JavaScript, justamente porque TypeScript não muda o comportamento de runtime da linguagem.

Então o caminho não deveria ser:

JavaScript
OU
TypeScript

Mas muitas vezes:

Diagrama
100%

Desenhando diagrama…

Mão: arraste para explorar · + / − para zoom · 0 para restaurar

✅ Resultado Final

Depois dessa comparação, podemos resumir assim:

JavaScript é excelente quando:

  1. O projeto é pequeno.
  2. Precisamos prototipar rapidamente.
  3. A lógica é simples.
  4. O código terá vida curta.
  5. Queremos aprender a base da plataforma web.

TypeScript começa a ganhar força quando:

  1. O projeto cresce.
  2. Existem muitos modelos de dados.
  3. Várias pessoas trabalham no código.
  4. Refatorações são frequentes.
  5. Existem muitas APIs.
  6. Criamos componentes reutilizáveis.
  7. O produto precisa ser mantido por anos.

🛠️ Considerações Finais

TypeScript não é um substituto para JavaScript.

Ele é uma ferramenta construída sobre JavaScript para adicionar análise estática e contratos ao desenvolvimento.

JavaScript oferece flexibilidade.

TypeScript tenta colocar limites onde essa flexibilidade pode virar incerteza.

Em um script pequeno, esses limites podem ser desnecessários.

Em uma aplicação com centenas de componentes, APIs e regras de negócio, eles podem economizar bastante tempo.

Por isso, em vez de perguntar:

TypeScript ou JavaScript: qual é melhor?

eu prefiro perguntar:

Quanto custa não ter contratos neste projeto?

Se a resposta for pequena, JavaScript pode ser perfeito.

Se a resposta envolver bugs difíceis, refatorações perigosas, APIs complexas e uma equipe grande, TypeScript começa a fazer muito sentido.

No final, a melhor escolha não é a linguagem que parece mais avançada.

É aquela que reduz a complexidade do produto que você realmente precisa construir.


Referências