TypeScript Avançado para Frontend: Técnicas Profissionais 2025

January 15, 2025 (1y ago)

Fala dev! 👋

TypeScript não é mais opcional no desenvolvimento frontend moderno. É uma ferramenta essencial que previne bugs, melhora a experiência de desenvolvimento e facilita a manutenção de código.

Neste guia avançado, vou te mostrar as técnicas mais poderosas do TypeScript que todo desenvolvedor frontend profissional precisa dominar.


🎯 Por que TypeScript avançado?

Benefícios reais:

Estatísticas impressionantes:


🔧 Generics - O poder da flexibilidade

1. Generics básicos

// ❌ Função sem generics (limitada)
function getFirstItem(items: any[]): any {
  return items[0]
}
 
// ✅ Com generics (flexível e type-safe)
function getFirstItem<T>(items: T[]): T | undefined {
  return items[0]
}
 
// Uso
const numbers = [1, 2, 3, 4, 5]
const firstNumber = getFirstItem(numbers) // number | undefined
 
const names = ['João', 'Maria', 'Pedro']
const firstName = getFirstItem(names) // string | undefined

2. Generics com constraints

// Constraint: T deve ter propriedade id
interface Identifiable {
  id: string
}
 
function updateItem<T extends Identifiable>(
  items: T[],
  id: string,
  updates: Partial<T>
): T[] {
  return items.map(item => 
    item.id === id ? { ...item, ...updates } : item
  )
}
 
// Uso
interface User extends Identifiable {
  name: string
  email: string
}
 
const users: User[] = [
  { id: '1', name: 'João', email: 'joao@email.com' },
  { id: '2', name: 'Maria', email: 'maria@email.com' }
]
 
const updatedUsers = updateItem(users, '1', { name: 'João Silva' })

3. Generics em React components

// Componente genérico para listas
interface ListProps<T> {
  items: T[]
  renderItem: (item: T, index: number) => React.ReactNode
  keyExtractor: (item: T) => string
  emptyMessage?: string
}
 
function List<T>({ 
  items, 
  renderItem, 
  keyExtractor, 
  emptyMessage = 'Nenhum item encontrado' 
}: ListProps<T>) {
  if (items.length === 0) {
    return <div className="text-gray-500">{emptyMessage}</div>
  }
 
  return (
    <div className="space-y-2">
      {items.map((item, index) => (
        <div key={keyExtractor(item)}>
          {renderItem(item, index)}
        </div>
      ))}
    </div>
  )
}
 
// Uso com diferentes tipos
interface Product {
  id: string
  name: string
  price: number
}
 
interface User {
  id: string
  name: string
  email: string
}
 
function App() {
  const products: Product[] = [
    { id: '1', name: 'Notebook', price: 2500 },
    { id: '2', name: 'Mouse', price: 50 }
  ]
 
  const users: User[] = [
    { id: '1', name: 'João', email: 'joao@email.com' },
    { id: '2', name: 'Maria', email: 'maria@email.com' }
  ]
 
  return (
    <div>
      <h2>Produtos</h2>
      <List
        items={products}
        keyExtractor={(product) => product.id}
        renderItem={(product) => (
          <div className="border p-2 rounded">
            <h3>{product.name}</h3>
            <p>R$ {product.price}</p>
          </div>
        )}
      />
 
      <h2>Usuários</h2>
      <List
        items={users}
        keyExtractor={(user) => user.id}
        renderItem={(user) => (
          <div className="border p-2 rounded">
            <h3>{user.name}</h3>
            <p>{user.email}</p>
          </div>
        )}
      />
    </div>
  )
}

🛠️ Utility Types - Ferramentas poderosas

1. Partial, Required, Pick, Omit

interface User {
  id: string
  name: string
  email: string
  age: number
  isActive: boolean
}
 
// Partial - todas as propriedades opcionais
type UserUpdate = Partial<User>
// Equivale a: { id?: string; name?: string; email?: string; age?: number; isActive?: boolean }
 
// Required - todas as propriedades obrigatórias
type UserRequired = Required<User>
// Todas as propriedades são obrigatórias
 
// Pick - selecionar propriedades específicas
type UserBasicInfo = Pick<User, 'id' | 'name' | 'email'>
// { id: string; name: string; email: string }
 
// Omit - excluir propriedades específicas
type UserWithoutId = Omit<User, 'id'>
// { name: string; email: string; age: number; isActive: boolean }
 
// Uso prático
function updateUser(id: string, updates: UserUpdate) {
  // Atualiza apenas as propriedades fornecidas
}
 
function createUser(userData: Omit<User, 'id'>) {
  // Cria usuário sem ID (gerado automaticamente)
  return {
    id: crypto.randomUUID(),
    ...userData
  }
}

2. Record, Exclude, Extract

// Record - criar objeto com chaves e valores tipados
type Theme = 'light' | 'dark'
type ColorScheme = Record<Theme, string>
 
const colors: ColorScheme = {
  light: '#ffffff',
  dark: '#000000'
}
 
// Exclude - excluir tipos de uma união
type AllColors = 'red' | 'green' | 'blue' | 'yellow'
type PrimaryColors = Exclude<AllColors, 'yellow'>
// 'red' | 'green' | 'blue'
 
// Extract - extrair tipos de uma união
type Status = 'loading' | 'success' | 'error' | 'idle'
type LoadingStates = Extract<Status, 'loading' | 'idle'>
// 'loading' | 'idle'

3. NonNullable, ReturnType, Parameters

// NonNullable - remover null e undefined
type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// string
 
// ReturnType - tipo de retorno de uma função
function getUser(id: string): Promise<User> {
  return fetch(`/api/users/${id}`).then(res => res.json())
}
 
type UserPromise = ReturnType<typeof getUser>
// Promise<User>
 
// Parameters - tipos dos parâmetros de uma função
type GetUserParams = Parameters<typeof getUser>
// [string]
 
// Uso prático
async function handleGetUser(...args: Parameters<typeof getUser>) {
  const [id] = args
  console.log('Buscando usuário:', id)
  return getUser(id)
}

🔄 Conditional Types - Lógica de tipos

1. Conditional Types básicos

// Sintaxe: T extends U ? X : Y
type IsString<T> = T extends string ? true : false
 
type Test1 = IsString<string> // true
type Test2 = IsString<number> // false
 
// Exemplo prático
type ApiResponse<T> = T extends string 
  ? { message: T }
  : { data: T }
 
type StringResponse = ApiResponse<string> // { message: string }
type DataResponse = ApiResponse<User[]> // { data: User[] }

2. Infer - inferir tipos

// Inferir tipo de retorno de uma função
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never
 
function getUser(): User { return {} as User }
function getUsers(): User[] { return [] }
 
type UserType = GetReturnType<typeof getUser> // User
type UsersType = GetReturnType<typeof getUsers> // User[]
 
// Inferir tipo de array
type ArrayElement<T> = T extends (infer U)[] ? U : never
 
type StringArray = string[]
type StringElement = ArrayElement<StringArray> // string
 
type NumberArray = number[]
type NumberElement = ArrayElement<NumberArray> // number

3. Mapped Types

// Transformar todas as propriedades de um tipo
type Optional<T> = {
  [K in keyof T]?: T[K]
}
 
type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}
 
// Exemplo prático
interface Product {
  id: string
  name: string
  price: number
  inStock: boolean
}
 
type OptionalProduct = Optional<Product>
// { id?: string; name?: string; price?: number; inStock?: boolean }
 
type ReadonlyProduct = Readonly<Product>
// { readonly id: string; readonly name: string; readonly price: number; readonly inStock: boolean }

🎨 Template Literal Types

1. String manipulation

// Capitalizar primeira letra
type Capitalize<S extends string> = S extends `${infer F}${infer R}`
  ? `${Uppercase<F>}${R}`
  : S
 
type CapitalizedName = Capitalize<'hello'> // 'Hello'
 
// Adicionar prefixo
type AddPrefix<T extends string, P extends string> = `${P}${T}`
 
type PrefixedId = AddPrefix<'user', 'id_'> // 'id_user'
 
// Exemplo prático
type EventName = 'click' | 'hover' | 'focus'
type EventHandler = `on${Capitalize<EventName>}`
 
// 'onClick' | 'onHover' | 'onFocus'

2. API Routes com template literals

// Definir rotas da API
type ApiRoute = `/api/${string}`
 
type UserRoute = `/api/users/${string}`
type ProductRoute = `/api/products/${string}`
 
// Função genérica para fazer requests
async function apiRequest<T>(
  url: ApiRoute,
  options?: RequestInit
): Promise<T> {
  const response = await fetch(url, options)
  if (!response.ok) {
    throw new Error(`API Error: ${response.status}`)
  }
  return response.json()
}
 
// Uso tipado
const user = await apiRequest<User>(`/api/users/${userId}`)
const products = await apiRequest<Product[]>(`/api/products`)

🔧 Advanced Patterns

1. Discriminated Unions

// Definir estados de loading com discriminação
type LoadingState = 
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: User[] }
  | { status: 'error'; error: string }
 
// Função que usa discriminated union
function handleLoadingState(state: LoadingState) {
  switch (state.status) {
    case 'idle':
      return 'Pronto para carregar'
    case 'loading':
      return 'Carregando...'
    case 'success':
      return `Carregados ${state.data.length} usuários` // TypeScript sabe que data existe
    case 'error':
      return `Erro: ${state.error}` // TypeScript sabe que error existe
  }
}

2. Branded Types

// Criar tipos únicos para evitar confusão
type UserId = string & { readonly __brand: 'UserId' }
type ProductId = string & { readonly __brand: 'ProductId' }
 
// Funções para criar branded types
function createUserId(id: string): UserId {
  return id as UserId
}
 
function createProductId(id: string): ProductId {
  return id as ProductId
}
 
// Uso - previne confusão entre IDs
function getUser(id: UserId): Promise<User> {
  // Só aceita UserId, não ProductId
}
 
function getProduct(id: ProductId): Promise<Product> {
  // Só aceita ProductId, não UserId
}
 
// Erro de compilação se misturar tipos
const userId = createUserId('123')
const productId = createProductId('456')
 
getUser(userId) // ✅ OK
getUser(productId) // ❌ Erro: Argument of type 'ProductId' is not assignable to parameter of type 'UserId'

3. Function Overloads

// Sobrecarga de funções
function createElement(tag: 'div'): HTMLDivElement
function createElement(tag: 'span'): HTMLSpanElement
function createElement(tag: 'button'): HTMLButtonElement
function createElement(tag: string): HTMLElement {
  return document.createElement(tag) as HTMLElement
}
 
// Uso com tipos específicos
const div = createElement('div') // HTMLDivElement
const span = createElement('span') // HTMLSpanElement
const button = createElement('button') // HTMLButtonElement

🧪 Testing com TypeScript

1. Tipos para testes

// Mock types
type MockFunction<T extends (...args: any[]) => any> = jest.MockedFunction<T>
 
// Mock de API
interface ApiClient {
  get<T>(url: string): Promise<T>
  post<T>(url: string, data: any): Promise<T>
}
 
type MockApiClient = {
  [K in keyof ApiClient]: MockFunction<ApiClient[K]>
}
 
// Test helper
function createMockApiClient(): MockApiClient {
  return {
    get: jest.fn(),
    post: jest.fn()
  }
}
 
// Uso nos testes
describe('UserService', () => {
  let mockApi: MockApiClient
 
  beforeEach(() => {
    mockApi = createMockApiClient()
  })
 
  it('should fetch user', async () => {
    const mockUser: User = { id: '1', name: 'João', email: 'joao@email.com' }
    mockApi.get.mockResolvedValue(mockUser)
 
    const user = await mockApi.get<User>('/users/1')
    
    expect(user).toEqual(mockUser)
    expect(mockApi.get).toHaveBeenCalledWith('/users/1')
  })
})

2. Test utilities

// Factory para criar dados de teste
type TestDataFactory<T> = (overrides?: Partial<T>) => T
 
function createUserFactory(): TestDataFactory<User> {
  return (overrides = {}) => ({
    id: '1',
    name: 'João Silva',
    email: 'joao@email.com',
    age: 30,
    isActive: true,
    ...overrides
  })
}
 
// Uso nos testes
describe('UserComponent', () => {
  const createUser = createUserFactory()
 
  it('should render user name', () => {
    const user = createUser({ name: 'Maria Santos' })
    render(<UserComponent user={user} />)
    
    expect(screen.getByText('Maria Santos')).toBeInTheDocument()
  })
 
  it('should handle inactive user', () => {
    const user = createUser({ isActive: false })
    render(<UserComponent user={user} />)
    
    expect(screen.getByText('Usuário inativo')).toBeInTheDocument()
  })
})

🚀 Configuração avançada

1. tsconfig.json otimizado

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/utils/*": ["./src/utils/*"],
      "@/types/*": ["./src/types/*"]
    },
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

2. ESLint com TypeScript

{
  "extends": [
    "next/core-web-vitals",
    "@typescript-eslint/recommended",
    "@typescript-eslint/recommended-requiring-type-checking"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-non-null-assertion": "warn",
    "@typescript-eslint/prefer-nullish-coalescing": "error",
    "@typescript-eslint/prefer-optional-chain": "error"
  }
}

📊 Performance e TypeScript

1. Type-only imports

// ❌ Import de valor desnecessário
import { User, createUser } from './user'
 
// ✅ Import apenas do tipo
import type { User } from './user'
import { createUser } from './user'
 
// ✅ Import de namespace
import type * as UserTypes from './user'

2. Const assertions

// ❌ Tipo inferido como string[]
const colors = ['red', 'green', 'blue']
 
// ✅ Tipo inferido como readonly tuple
const colors = ['red', 'green', 'blue'] as const
 
// Uso com template literals
const themes = ['light', 'dark'] as const
type Theme = typeof themes[number] // 'light' | 'dark'

✅ Checklist TypeScript Avançado

Generics:

Utility Types:

Conditional Types:

Padrões Avançados:

Configuração:


🎯 Conclusão

TypeScript avançado não é apenas sobre tipos, é sobre criar código mais robusto, maintível e expressivo. As técnicas mostradas aqui vão elevar seu nível como desenvolvedor frontend.

Principais benefícios:

Próximos passos:

  1. Pratique generics em projetos reais
  2. Implemente utility types no seu código
  3. Experimente conditional types
  4. Configure TypeScript strict mode

Lembre-se: TypeScript é uma ferramenta de produtividade, não um obstáculo! 🚀

Allisson Lima
Desenvolvedor Frontend | Especialista em TypeScript e Arquitetura