Voltar para o Blog
Anúncio

7 Técnicas Avançadas de TypeScript que Você Deveria Conhecer em 2025

Olá HaWkers, TypeScript se consolidou como a escolha padrão para projetos JavaScript sérios. Em 2025, mais de 78% dos novos projetos web utilizam TypeScript desde o início, segundo pesquisas recentes da comunidade.

Você domina o básico de TypeScript mas sente que está apenas arranhando a superfície? Este artigo vai além de interfaces e tipos básicos, explorando técnicas que separaram desenvolvedores intermediários de especialistas em TypeScript.

1. Strict Mode: A Base de Código Seguro

O modo strict do TypeScript não é uma técnica única, mas um conjunto de flags que tornam seu código drasticamente mais seguro. Em 2025, projetos sem strict: true são considerados legacy.

// tsconfig.json - Configuração moderna
{
  "compilerOptions": {
    "strict": true, // Ativa todas as flags strict
    "noUncheckedIndexedAccess": true, // TS 5.0+
    "noPropertyAccessFromIndexSignature": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true
  }
}

O que muitos não percebem é que strict: true ativa várias flags individualmente:

  • noImplicitAny: Proíbe any implícito
  • strictNullChecks: null e undefined são tipos distintos
  • strictFunctionTypes: Verificação contravariant de parâmetros
  • strictBindCallApply: Tipagem correta para .bind(), .call(), .apply()
  • strictPropertyInitialization: Propriedades de classe devem ser inicializadas
  • noImplicitThis: this deve ter tipo explícito em contextos ambíguos

Mas o verdadeiro poder vem das flags adicionais como noUncheckedIndexedAccess, introduzida em versões mais recentes.

// Sem noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User
user.name; // ❌ Runtime error: Cannot read property 'name' of undefined

// Com noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User | undefined
// user.name; // ❌ Erro de compilação
user?.name; // ✅ Seguro

Esta flag sozinha previne incontáveis bugs em produção relacionados a acessos a objetos que podem não existir.

Anúncio

2. Template Literal Types: Tipos Dinâmicos Poderosos

Template Literal Types, introduzidos no TypeScript 4.1 e expandidos em versões posteriores, permitem criar tipos baseados em strings de forma dinâmica.

// Criando tipos dinâmicos para eventos
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// Type: 'onClick' | 'onFocus' | 'onBlur'

type EventHandlers = {
  [K in HandlerName]: (event: Event) => void;
};

// Uso prático
const handlers: EventHandlers = {
  onClick: (e) => console.log('Clicked'),
  onFocus: (e) => console.log('Focused'),
  onBlur: (e) => console.log('Blurred')
};

Isso é extremamente poderoso para APIs que seguem convenções de nomenclatura. Veja um exemplo real com rotas de API:

// Sistema de rotas type-safe
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Resource = 'user' | 'product' | 'order';

type Route = `/${Resource}` | `/${Resource}/${string}`;
type RouteHandler<M extends HTTPMethod, R extends Route> = {
  method: M;
  route: R;
  handler: (req: Request) => Promise<Response>;
};

// Uso com autocomplete total
const userRoute: RouteHandler<'GET', '/user'> = {
  method: 'GET',
  route: '/user',
  handler: async (req) => {
    // Implementation
    return new Response();
  }
};

// ❌ Erro de compilação: 'PATCH' não existe em HTTPMethod
// const invalidRoute: RouteHandler<'PATCH', '/user'> = { ... };

typescript types autocomplete

Anúncio

3. Conditional Types e Inferência: Lógica em Tipos

Conditional Types permitem criar lógica complexa no sistema de tipos, similar a operadores ternários em JavaScript.

// Type que extrai o tipo de retorno de uma Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number

// Aplicação prática: função que retorna Promise ou valor direto
async function fetchData<T>(
  useCache: boolean,
  data: T
): Promise<UnwrapPromise<T>> {
  if (useCache) {
    return data as UnwrapPromise<T>;
  }
  // Simula fetch assíncrono
  return new Promise(resolve =>
    setTimeout(() => resolve(data as UnwrapPromise<T>), 100)
  ) as any;
}

Um padrão avançado é combinar conditional types com mapped types:

// Transforma propriedades opcionais em obrigatórias e vice-versa
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type RequiredKeys<T, K extends keyof T> = T & {
  [P in K]-?: T[P];
};

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

// Torna name e email obrigatórios
type FullUser = RequiredKeys<User, 'name' | 'email'>;
// Type: { id: string; name: string; email: string; }

Este padrão é incrivelmente útil para validação de formulários e DTOs onde certas propriedades tornam-se obrigatórias em contextos específicos.

4. Branded Types: Segurança em Tipos Primitivos

Branded Types (ou Nominal Types) resolvem um problema comum: como diferenciar strings com significados diferentes?

// Problema: userId e productId são ambos strings
function getUser(id: string) { /* ... */ }
function getProduct(id: string) { /* ... */ }

const userId = "user123";
const productId = "prod456";

getUser(productId); // ❌ Bug, mas TypeScript não reclama

Solução com Branded Types:

// Criando tipos branded
type Brand<K, T> = K & { __brand: T };

type UserId = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;

// Funções de criação (smart constructors)
function createUserId(id: string): UserId {
  // Validação se necessário
  return id as UserId;
}

function createProductId(id: string): ProductId {
  return id as ProductId;
}

// Uso type-safe
function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }

const userId = createUserId("user123");
const productId = createProductId("prod456");

getUser(userId); // ✅ OK
// getUser(productId); // ❌ Erro de compilação!

Isso é especialmente valioso em sistemas financeiros onde você trabalha com diferentes moedas ou IDs de diferentes domínios.

Anúncio

5. Utility Types Avançados: Além do Partial e Pick

TypeScript oferece utility types poderosos, mas a maioria dos desenvolvedores usa apenas Partial, Pick e Omit. Vamos além:

// ReturnType: Extrai tipo de retorno de função
function createUser() {
  return {
    id: '123',
    name: 'John',
    createdAt: new Date()
  };
}

type User = ReturnType<typeof createUser>;
// Type: { id: string; name: string; createdAt: Date; }

// Parameters: Extrai tipos de parâmetros de função
function updateUser(id: string, data: { name: string; email: string }) {
  // Implementation
}

type UpdateUserParams = Parameters<typeof updateUser>;
// Type: [id: string, data: { name: string; email: string; }]

// Awaited: Unwrap Promise types (TS 4.5+)
type Response = Awaited<Promise<{ data: string }>>;
// Type: { data: string; }

// Combinando utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserKeys = keyof User; // 'id' | 'name' | 'createdAt'

Um padrão poderoso é criar seus próprios utility types customizados:

// DeepPartial: Torna todas as propriedades aninhadas opcionais
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object
    ? DeepPartial<T[P]>
    : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
    credentials: {
      user: string;
      password: string;
    };
  };
  cache: {
    ttl: number;
  };
}

// Permite updates parciais profundos
function updateConfig(config: DeepPartial<Config>) {
  // Você pode passar apenas o que quer atualizar
}

updateConfig({
  database: {
    credentials: {
      password: 'new-password' // Apenas password, resto é opcional
    }
  }
});
Anúncio

6. Discriminated Unions: Type Guards Automáticos

Discriminated Unions (ou Tagged Unions) são um padrão que torna impossible states impossível de representar.

// Estado de requisição usando union discriminada
type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function handleRequest<T>(state: RequestState<T>) {
  // TypeScript estreita o tipo automaticamente
  switch (state.status) {
    case 'idle':
      // state.status é 'idle'
      // state.data não existe (erro de compilação se tentar acessar)
      return 'Aguardando...';

    case 'loading':
      return 'Carregando...';

    case 'success':
      // TypeScript sabe que state.data existe aqui!
      return `Sucesso: ${state.data}`;

    case 'error':
      // TypeScript sabe que state.error existe aqui!
      return `Erro: ${state.error.message}`;
  }
}

// Uso em componentes React
function UserProfile() {
  const [userState, setUserState] =
    useState<RequestState<User>>({ status: 'idle' });

  useEffect(() => {
    setUserState({ status: 'loading' });

    fetchUser()
      .then(data => setUserState({ status: 'success', data }))
      .catch(error => setUserState({ status: 'error', error }));
  }, []);

  return <div>{handleRequest(userState)}</div>;
}

Este padrão elimina bugs comuns onde você acessa data quando está em estado de loading, ou tenta mostrar error quando na verdade tem sucesso.

7. Type Predicates: Type Guards Customizados

Type predicates permitem criar funções que informam ao TypeScript sobre o tipo de um valor em runtime.

// Type predicate básico
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// Uso
function processValue(value: string | number) {
  if (isString(value)) {
    // TypeScript sabe que value é string aqui
    console.log(value.toUpperCase());
  } else {
    // TypeScript sabe que value é number aqui
    console.log(value.toFixed(2));
  }
}

// Type predicate avançado com validação
interface User {
  id: string;
  name: string;
  email: string;
}

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj &&
    typeof (obj as any).id === 'string' &&
    typeof (obj as any).name === 'string' &&
    typeof (obj as any).email === 'string'
  );
}

// Uso com API responses
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();

  if (!isUser(data)) {
    throw new Error('Invalid user data from API');
  }

  // TypeScript sabe que data é User aqui
  return data;
}

typescript validation

Anúncio

Bônus: Integração com Zod para Runtime Validation

TypeScript verifica tipos em compile-time, mas APIs retornam dados em runtime. Zod fecha essa lacuna:

import { z } from 'zod';

// Schema Zod
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().int().positive().optional()
});

// TypeScript type inferido automaticamente
type User = z.infer<typeof UserSchema>;

// Validação em runtime com type narrowing
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();

  // Parse valida e retorna User ou lança erro
  return UserSchema.parse(data);
}

// Validação segura sem exceções
const result = UserSchema.safeParse(unknownData);

if (result.success) {
  // result.data é User
  console.log(result.data.name);
} else {
  // result.error contém detalhes
  console.error(result.error.errors);
}

Esta combinação de TypeScript com Zod é o padrão-ouro em 2025 para aplicações que lidam com dados externos.

O Impacto Real Dessas Técnicas

Implementar essas técnicas não é sobre escrever código "mais bonito" — é sobre prevenir bugs. Em um estudo de 2024, times que adotaram strict mode + branded types + discriminated unions reportaram:

  • 38% menos bugs em produção relacionados a tipos incorretos
  • 22% de redução em tempo de debugging
  • Aumento de 15% na confiança ao refatorar código

TypeScript bem usado transforma seu editor em um assistente que previne erros antes mesmo de você executar o código.

Quer entender melhor os fundamentos de JavaScript que tornam TypeScript possível? Confira meu artigo sobre Programação Funcional em JavaScript onde você vai descobrir conceitos que TypeScript utiliza para inferência de tipos.

Bora pra cima! 🦅

📚 Quer Aprofundar Seus Conhecimentos em JavaScript?

Este artigo cobriu técnicas avançadas de TypeScript, mas há muito mais para explorar no mundo do desenvolvimento moderno.

Desenvolvedores que investem em conhecimento sólido e 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:

  • 3x de R$34,54 no cartão
  • ou R$97,90 à vista

👉 Conhecer o Guia JavaScript

💡 Material atualizado com as melhores práticas do mercado

Anúncio
Post anteriorPróximo post

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário