TypeScript Avançado: Patterns que Todo Dev Deveria Conhecer
TypeScript
2 de janeiro de 2024
10 min de leitura

TypeScript Avançado: Patterns que Todo Dev Deveria Conhecer

Explorando utility types, conditional types e outras funcionalidades avançadas do TypeScript para escrever código mais robusto.

#TypeScript #JavaScript #Tipos #Patterns #Desenvolvimento
TypeScript Avançado: Patterns que Todo Dev Deveria Conhecer

TypeScript Avançado: Patterns que Todo Dev Deveria Conhecer

Depois de anos usando TypeScript em projetos enterprise, descobri que conhecer esses patterns avançados é o que separa um desenvolvedor iniciante de um expert.

1. Utility Types na Prática

Pick e Omit - Criando Tipos Derivados

interface User {
  id: string;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
}

// ✅ Para APIs públicas - remove dados sensíveis
type PublicUser = Omit<User, "password">;

// ✅ Para formulários - pega apenas campos editáveis
type UserForm = Pick<User, "name" | "email">;

// ✅ Para criação - remove campos auto-gerados
type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;

Partial e Required - Flexibilidade em Updates

// ✅ Para updates parciais
const updateUser = (id: string, updates: Partial<User>) => {
  // Apenas os campos fornecidos serão atualizados
};

// ✅ Para garantir campos obrigatórios
type UserWithRequiredEmail = Required<Pick<User, "email">> & Partial<User>;

2. Conditional Types - Lógica a Nível de Tipos

// Pattern: Extrair tipos de arrays
type ArrayElement<T> = T extends (infer U)[] ? U : never;

type StringArray = string[];
type ElementType = ArrayElement<StringArray>; // string

// Pattern: Tipos baseados em condições
type APIResponse<T> = T extends string ? { message: T } : { data: T };

type ErrorResponse = APIResponse<string>; // { message: string }
type DataResponse = APIResponse<User[]>; // { data: User[] }

Promise Unwrapping

// Extrair tipo de dentro de uma Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;

async function fetchUser(): Promise<User> {
  // implementação
}

type UserType = Awaited<ReturnType<typeof fetchUser>>; // User

3. Mapped Types - Transformações Poderosas

Criando Validators

type Validator<T> = {
  [K in keyof T]: (value: T[K]) => boolean;
};

const userValidator: Validator<User> = {
  id: (value) => typeof value === "string" && value.length > 0,
  name: (value) => typeof value === "string" && value.length > 2,
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  // ... outros campos
};

Event Handlers com Prefix

type EventHandlers<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type UserHandlers = EventHandlers<User>;
// Resultado:
// {
//   onId: (value: string) => void;
//   onName: (value: string) => void;
//   onEmail: (value: string) => void;
// }

4. Template Literal Types - Strings Tipadas

// URLs tipadas
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "/users" | "/posts" | "/comments";
type APIRoute = `${HttpMethod} ${Endpoint}`;

// CSS Properties tipadas
type CSSUnit = "px" | "rem" | "em" | "%";
type Size = `${number}${CSSUnit}`;

const margin: Size = "16px"; // ✅
const padding: Size = "1.5rem"; // ✅
const width: Size = "100%"; // ✅

5. Generics Avançados com Constraints

Repository Pattern Tipado

interface Entity {
  id: string;
}

interface Repository<T extends Entity> {
  findById(id: string): Promise<T | null>;
  save(entity: Omit<T, "id">): Promise<T>;
  update(id: string, updates: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}

// ✅ Garante que User tem id
const userRepository: Repository<User> = {
  // implementação garantida pela interface
};

API Client Tipado

type APIEndpoints = {
  "/users": { GET: User[]; POST: CreateUser };
  "/posts": { GET: Post[]; POST: CreatePost };
};

class APIClient {
  async request<
    TPath extends keyof APIEndpoints,
    TMethod extends keyof APIEndpoints[TPath]
  >(
    path: TPath,
    method: TMethod,
    data?: TMethod extends "POST" ? APIEndpoints[TPath][TMethod] : never
  ): Promise<APIEndpoints[TPath][TMethod]> {
    // implementação
  }
}

// ✅ Totalmente tipado
const users = await api.request("/users", "GET"); // User[]
const newUser = await api.request("/users", "POST", userData); // User

6. Discriminated Unions - Estados Complexos

type LoadingState = {
  status: "loading";
};

type SuccessState = {
  status: "success";
  data: User[];
};

type ErrorState = {
  status: "error";
  error: string;
};

type AsyncState = LoadingState | SuccessState | ErrorState;

// ✅ TypeScript sabe exatamente qual tipo está disponível
const handleState = (state: AsyncState) => {
  switch (state.status) {
    case "loading":
      // state.data não existe aqui ✅
      return <Spinner />;
    case "success":
      // state.data existe e é User[] ✅
      return <UserList users={state.data} />;
    case "error":
      // state.error existe e é string ✅
      return <Error message={state.error} />;
  }
};

7. Type Guards - Validação em Runtime

// Type Guard simples
const isString = (value: unknown): value is string => {
  return typeof value === "string";
};

// Type Guard para objetos
const isUser = (obj: unknown): obj is User => {
  return (
    typeof obj === "object" &&
    obj !== null &&
    "id" in obj &&
    "name" in obj &&
    "email" in obj
  );
};

// Uso prático
const processUserData = (data: unknown) => {
  if (isUser(data)) {
    // TypeScript sabe que data é User aqui ✅
    console.log(data.name);
  }
};

8. Patterns para React + TypeScript

Component Props com Variants

type ButtonVariant = "primary" | "secondary" | "danger";

type ButtonProps = {
  variant: ButtonVariant;
  children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

const Button: React.FC<ButtonProps> = ({
  variant,
  children,
  className,
  ...rest
}) => {
  const baseClasses = "px-4 py-2 rounded";
  const variantClasses = {
    primary: "bg-blue-500 text-white",
    secondary: "bg-gray-500 text-white",
    danger: "bg-red-500 text-white",
  };

  return (
    <button
      className={`${baseClasses} ${variantClasses[variant]} ${className}`}
      {...rest}
    >
      {children}
    </button>
  );
};

Resultados na Prática

Aplicando esses patterns nos projetos:

  • 90% menos bugs relacionados a tipos
  • Refatoração 5x mais segura
  • Developer Experience muito melhor
  • Documentação automática via tipos

Ferramentas Essenciais

  1. TypeScript Playground - Para testar tipos
  2. ts-node - Para execução direta
  3. @typescript-eslint - Linting avançado
  4. Type Coverage - Métricas de tipagem

Conclusão

TypeScript avançado não é sobre complexidade, é sobre expressividade e segurança. Esses patterns vão transformar a qualidade do seu código.

Próximo passo: Experimente implementar um Repository pattern tipado no seu próximo projeto.


Qual pattern você achou mais útil? Tem alguma dúvida sobre implementação? Compartilhe nos comentários!

Logo Renato Khael

Renato Khael

Engenheiro de Software

Gostou do artigo?

Vamos conversar sobre seu próximo projeto ou trocar ideias sobre tecnologia

WhatsApp