TypeScript em 2025: Por Que 38.5% dos Desenvolvedores Escolheram e Você Deveria Também
Olá HaWkers, TypeScript está em 38.5% de popularidade segundo pesquisas de 2025, consolidando-se como uma das top 5 linguagens de programação do mundo. Mas será que é só hype ou há razões sólidas para adotar?
Neste artigo, vamos explorar por que TypeScript dominou o ecossistema JavaScript, como começar de forma prática e estratégias de migração para projetos existentes.
Por Que TypeScript Venceu a Batalha
1. Segurança de Tipos Previne Bugs Caros
// ❌ JavaScript: Bug só aparece em produção
function calculateDiscount(price, percentage) {
return price - (price * percentage / 100);
}
// Bug: alguém passa string por engano
calculateDiscount('100', 20); // '100-NaN' = NaN 😱
// ✅ TypeScript: Erro detectado em tempo de desenvolvimento
function calculateDiscount(price: number, percentage: number): number {
return price - (price * percentage / 100);
}
// Erro em tempo de compilação!
calculateDiscount('100', 20);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'2. IntelliSense e Autocomplete Poderosos
// TypeScript fornece autocomplete preciso
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
createdAt: Date;
metadata?: {
lastLogin?: Date;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
};
}
function greetUser(user: User) {
// IDE sugere todas propriedades disponíveis
console.log(`Hello, ${user.name}!`);
// Autocomplete em objetos aninhados
if (user.metadata?.preferences.theme === 'dark') {
// IDE sabe exatamente o que está disponível
console.log('Dark mode enabled');
}
// Tipos literais previnem typos
if (user.role === 'admim') { // Erro: 'admim' não existe
// ...
}
}3. Refactoring Seguro em Grandes Codebases
// Cenário: Renomear propriedade em 100 arquivos
// Interface original
interface Product {
productName: string;
price: number;
}
// Refatorar para 'name' em vez de 'productName'
interface Product {
name: string; // TypeScript mostra TODOS os lugares que quebraram
price: number;
}
// Todos os usos são automaticamente identificados
function displayProduct(product: Product) {
return `${product.productName} - $${product.price}`;
// Erro: Property 'productName' does not exist on type 'Product'
// Sugestão: Did you mean 'name'?
}
// Em JavaScript: Você descobriria esse bug em produção 💥
TypeScript na Prática: Do Básico ao Avançado
Tipos Primitivos e Básicos
// Tipos básicos
let isDone: boolean = false;
let count: number = 42;
let name: string = 'John';
let notSure: any = 4; // Evite usar 'any'!
// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];
// Tuplas (arrays com tipos fixos)
let tuple: [string, number] = ['age', 30];
// Enum
enum Status {
Pending = 'PENDING',
Approved = 'APPROVED',
Rejected = 'REJECTED'
}
const orderStatus: Status = Status.Pending;
// Union Types (tipos alternativos)
let id: string | number;
id = '123'; // OK
id = 123; // OK
id = true; // Erro
// Literal Types (valores específicos)
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
function makeRequest(url: string, method: HttpMethod) {
// method só pode ser um dos valores especificados
}
makeRequest('/api/users', 'GET'); // OK
makeRequest('/api/users', 'PATCH'); // ErroInterfaces e Type Aliases
// Interface: Define estrutura de objetos
interface User {
id: string;
name: string;
email: string;
age?: number; // Opcional
readonly createdAt: Date; // Readonly
}
// Extending interfaces
interface AdminUser extends User {
permissions: string[];
role: 'admin';
}
// Type Alias: Similar a interface, mas mais flexível
type Point = {
x: number;
y: number;
};
// Type para union types
type ID = string | number;
// Type para funções
type GreetFunction = (name: string) => string;
const greet: GreetFunction = (name) => `Hello, ${name}!`;
// Interface vs Type: Quando usar?
// Use Interface para objetos que podem ser estendidos
// Use Type para unions, primitivos, tuplas
// Intersection Types (combinar tipos)
type WithTimestamp = {
createdAt: Date;
updatedAt: Date;
};
type UserWithTimestamp = User & WithTimestamp;
const user: UserWithTimestamp = {
id: '1',
name: 'John',
email: 'john@example.com',
createdAt: new Date(),
updatedAt: new Date()
};Generics: Tipos Reutilizáveis
// Generic simples
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>('hello');
// Generic com constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length); // OK, T tem length
return arg;
}
logLength('hello'); // OK, string tem length
logLength([1, 2, 3]); // OK, array tem length
logLength(42); // Erro, number não tem length
// Generic em interfaces
interface Response<T> {
data: T;
status: number;
message: string;
}
interface User {
id: string;
name: string;
}
const userResponse: Response<User> = {
data: { id: '1', name: 'John' },
status: 200,
message: 'Success'
};
const usersResponse: Response<User[]> = {
data: [
{ id: '1', name: 'John' },
{ id: '2', name: 'Jane' }
],
status: 200,
message: 'Success'
};
// Generic utility types
type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// Uso prático
interface TodoItem {
id: string;
title: string;
completed: boolean;
createdAt: Date;
}
// Partial: Todos campos opcionais
type TodoUpdate = Partial<TodoItem>;
function updateTodo(id: string, updates: TodoUpdate) {
// Pode passar apenas os campos que quer atualizar
}
updateTodo('1', { completed: true }); // OK
updateTodo('2', { title: 'New title', completed: false }); // OK
// Pick: Selecionar apenas campos específicos
type TodoSummary = Pick<TodoItem, 'id' | 'title'>;
const summary: TodoSummary = {
id: '1',
title: 'Task'
// completed e createdAt não são necessários
};
Casos de Uso Avançados
// 1. Discriminated Unions (Pattern Matching)
type Success<T> = {
type: 'success';
data: T;
};
type Error = {
type: 'error';
error: string;
};
type Result<T> = Success<T> | Error;
function handleResult<T>(result: Result<T>) {
// TypeScript sabe exatamente qual tipo baseado em 'type'
if (result.type === 'success') {
console.log(result.data); // OK, TypeScript sabe que tem 'data'
} else {
console.log(result.error); // OK, TypeScript sabe que tem 'error'
}
}
// 2. Conditional Types
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
// 3. Mapped Types
type Optional<T> = {
[K in keyof T]?: T[K];
};
type RequiredUser = {
name: string;
email: string;
age: number;
};
type OptionalUser = Optional<RequiredUser>;
// { name?: string; email?: string; age?: number; }
// 4. Template Literal Types (TypeScript 4.1+)
type HTTPMethod = 'GET' | 'POST' | 'PUT';
type Route = '/users' | '/products' | '/orders';
type APIEndpoint = `${HTTPMethod} ${Route}`;
// 'GET /users' | 'GET /products' | 'GET /orders' |
// 'POST /users' | 'POST /products' | 'POST /orders' |
// 'PUT /users' | 'PUT /products' | 'PUT /orders'
// 5. Type Guards
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // OK, TypeScript sabe que é string
}
}
// 6. Decorators (Experimental)
function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@logged
add(a: number, b: number): number {
return a + b;
}
}Migração Gradual de JavaScript para TypeScript
Estratégia de Migração
// 1. Configuração inicial (tsconfig.json)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"jsx": "react-jsx",
"strict": false, // Começar com false, habilitar gradualmente
"allowJs": true, // Permitir arquivos .js durante migração
"checkJs": false, // Não checar arquivos .js inicialmente
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src"],
"exclude": ["node_modules"]
}
// 2. Plano de migração
const migrationPlan = {
'Fase 1 - Setup': [
'Instalar TypeScript e tipos',
'Configurar tsconfig.json',
'Renomear 1 arquivo .js → .ts para testar',
'Configurar build pipeline'
],
'Fase 2 - Arquivos Novos': [
'Todos arquivos novos em TypeScript',
'Criar types.d.ts para interfaces comuns'
],
'Fase 3 - Migração Gradual': [
'Migrar módulos utilitários primeiro',
'Migrar componentes independentes',
'Migrar componentes com dependências',
'Habilitar strict mode gradualmente'
],
'Fase 4 - Strictness': [
'Habilitar noImplicitAny',
'Habilitar strictNullChecks',
'Habilitar strictFunctionTypes',
'Habilitar strictPropertyInitialization'
]
};
// 3. Tipos para bibliotecas externas
// Instalar @types packages
npm install --save-dev @types/react @types/node @types/express
// 4. Criar arquivos de declaração para código legado
// legacy.d.ts
declare module 'old-library' {
export function doSomething(param: string): void;
}
// 5. Utility types para migração
// any-to-unknown.ts
type TODO = any; // Marca temporária, substituir depois
type FIXME = any; // Marca bugs conhecidos
// 6. Exemplo de migração progressiva
// Antes (JavaScript)
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Passo 1: Adicionar JSDoc (preparação)
/**
* @param {Array<{price: number, quantity: number}>} items
* @returns {number}
*/
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Passo 2: Converter para TypeScript
interface CartItem {
price: number;
quantity: number;
}
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Passo 3: Adicionar validação em runtime (defense in depth)
function calculateTotal(items: CartItem[]): number {
if (!Array.isArray(items)) {
throw new Error('Items must be an array');
}
return items.reduce((sum, item) => {
if (typeof item.price !== 'number' || typeof item.quantity !== 'number') {
throw new Error('Invalid item format');
}
return sum + item.price * item.quantity;
}, 0);
}
TypeScript com Frameworks Populares
React + TypeScript
import React, { useState, useEffect } from 'react';
// Props tipadas
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
avatar?: string;
};
onEdit?: (userId: string) => void;
onDelete?: (userId: string) => void;
}
// Componente funcional tipado
export function UserCard({ user, onEdit, onDelete }: UserCardProps) {
const [isHovered, setIsHovered] = useState<boolean>(false);
useEffect(() => {
console.log('User card mounted:', user.id);
return () => {
console.log('User card unmounted:', user.id);
};
}, [user.id]);
const handleEdit = () => {
onEdit?.(user.id); // Safe navigation operator
};
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={isHovered ? 'card-hovered' : 'card'}
>
{user.avatar && <img src={user.avatar} alt={user.name} />}
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={handleEdit}>Edit</button>
{onDelete && (
<button onClick={() => onDelete(user.id)}>Delete</button>
)}
</div>
);
}
// Hooks customizados tipados
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
}
} catch (err) {
if (!cancelled) {
setError(err as Error);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
return { user, loading, error };
}Next.js + TypeScript
// app/page.tsx (App Router)
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Home Page',
description: 'Welcome to my site'
};
interface HomePageProps {
searchParams: { [key: string]: string | string[] | undefined };
}
export default async function HomePage({ searchParams }: HomePageProps) {
// Server component tipado
const data = await fetchData();
return (
<div>
<h1>Welcome</h1>
<DataDisplay data={data} />
</div>
);
}
// API Route tipada
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface UserPayload {
name: string;
email: string;
}
export async function POST(request: NextRequest) {
try {
const body: UserPayload = await request.json();
// Validação
if (!body.name || !body.email) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// Criar usuário
const user = await createUser(body);
return NextResponse.json(user, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}Ferramentas Essenciais do Ecossistema TypeScript
// 1. ESLint + TypeScript
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
parserOptions: {
project: './tsconfig.json'
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off'
}
};
// 2. Prettier
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100
}
// 3. Husky + lint-staged (pre-commit hooks)
// package.json
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
// 4. ts-node para execução direta
// Rodar TypeScript diretamente sem compilar
npx ts-node script.ts
// 5. Type testing com tsd
import { expectType } from 'tsd';
// Testar se tipos estão corretos
expectType<number>(calculateTotal([{ price: 10, quantity: 2 }]));
Conclusão: Vale a Pena Investir em TypeScript?
Sim, absolutamente. TypeScript não é mais uma tendência — é o novo padrão para JavaScript profissional em 2025.
Benefícios concretos:
- Menos bugs: Erros detectados antes de produção
- Melhor DX: Autocomplete e refactoring seguros
- Código mais maintível: Documentação viva via tipos
- Carreira: 68% das vagas exigem TypeScript
Se você quer dominar JavaScript e TypeScript de forma estruturada, recomendo que dê uma olhada em outro artigo: Programação Funcional no JavaScript: Entendendo Higher-Order Functions onde você vai descobrir padrões que funcionam perfeitamente com TypeScript.
Bora pra cima! 🦅
📚 Quer Aprofundar Seus Conhecimentos em JavaScript?
Este artigo cobriu TypeScript, mas dominar JavaScript sólido é a base para tudo.
Desenvolvedores que investem em conhecimento estruturado tendem a ter mais oportunidades no mercado.
Material de Estudo Completo
Se você quer dominar JavaScript do básico ao avançado, preparei um guia completo:
Opções de investimento:
- R$9,90 (pagamento único)
💡 Material atualizado com as melhores práticas do mercado

