React: Limites de erros e depuração

Última atualização: 2026-08-26

Tom era responsável pelo back-end do e-commerce, que apresentou um problema de “tela branca” no site em produção — os usuários relataram que a página de detalhes do pedido ficava completamente em branco ao ser aberta, e o console exibia o erro Cannot read properties of indefinido (reading 'name'). A causa foi um erro de JavaScript não interceptado, gerado por um componente filho durante a fase de renderização, o que fez com que toda a árvore de componentes do React travasse. Tom precisa estabelecer um sistema de isolamento de erros dentro do aplicativo para garantir que a falha de componentes individuais não afete a usabilidade geral da página.


1. O que você vai aprender



2. Diagramas conceituais

A figura abaixo ilustra o processo de captura do Limite de Erro e a cadeia de análise do Profiler:

100%
flowchart TD
    A[React Component tree] --> B[Error Boundary]

    B --> C{Is there an error in the rendering of the child component??}
    C -->|No| D[Normal Rendering]
    C -->|is | E[getDerivedStateFromError]

    E --> F[Update state.hasError = true]
    F --> G[Rendering fallback UI]

    G --> H{Click to retry??}
    H -->|is | I[Reset state]
    I --> A
    H -->|No| J[Stay in the lower division UI]

    K[React DevTools Profiler] --> L[Recording Component Rendering]
    L --> M[Flame Pattern Analysis]
    M --> N["Identify Time-Consuming Components(Yellow/Red)"]
    N --> O[React.memo / useMemo Optimization]

    style B fill:#e3f2fd,stroke:#1565c0
    style E fill:#fff3e0,stroke:#e65100
    style G fill:#e8f5e9,stroke:#2e7d32
    style K fill:#f3e5f5,stroke:#7b1fa2


3. Um cenário da vida real

Nível de tratamento de erros Escopo Estratégia de recuperação Cenários aplicáveis
try/catch Operação assíncrona única Repetição/Reserva Solicitações de API, operações com Promise
Limite de erro Erro de renderização da árvore de componentes filhos Interface de usuário alternativa + botão “Tentar novamente” Proteção contra tela branca do componente
Rejeições não tratadas globalmente Erros de Promise não capturados Relatórios de log Monitoramento abrangente
window.onerror Erros de sincronização global Relatórios de log Monitoramento abrangente
React DevTools Depuração durante o desenvolvimento Resolução de problemas Resolução de problemas de desempenho e renderização

A página de detalhes do pedido do Tom é composta pelos seguintes componentes:

TEXT 📖 Somente leitura
OrderPage
  ├── OrderHeader      (Order Number、Status)
  ├── OrderItems       (Product List)
  │   └── OrderItem    × N(Single Item,Includes price calculation)
  ├── ShippingInfo     (Shipping Information)
  └── PaymentInfo      (Payment Information)

A causa do incidente online foi a ausência do campo price nos dados do produto de um pedido específico; quando o componente OrderItem acessou item.price.toFixed(2), ocorreu um erro TypeError, fazendo com que todo o OrderPage exibisse uma tela em branco.

A abordagem correta é envolver a área OrderItems com um Error Boundary, de modo que, mesmo que a lista de produtos não seja renderizada, o cabeçalho do pedido e as informações de pagamento continuem sendo exibidos corretamente. Além disso, Tom precisa aprender a usar o Profiler do React DevTools para identificar gargalos de desempenho.


(1) Limite de erro — Rede de segurança no nível do componente

Um Error Boundary é um mecanismo declarativo de tratamento de erros fornecido pelo React. Quando qualquer componente em uma subárvore gera um erro durante a fase de renderização, em um método do ciclo de vida ou em seu construtor, o Error Boundary pode interceptar esse erro e exibir uma interface de usuário alternativa, em vez de fazer com que todo o aplicativo exiba uma tela em branco.

Observação: Atualmente, o Error Boundary só pode ser implementado usando componentes de classe (o React planeja disponibilizar uma versão com Hooks em versões futuras).

▶ Exemplo 1: Componente ErrorBoundary genérico

TSX 📖 Somente leitura
import { Component, ErrorInfo, ReactNode } from 'react'

interface ErrorBoundaryProps {
  children: ReactNode
  /** Custom Downgrade UI */
  fallback?: ReactNode
  /** Error Callback(Submit Sentry etc.) */
  onError?: (error: Error, errorInfo: ErrorInfo) => void
}

interface ErrorBoundaryState {
  hasError: boolean
  error: Error | null
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props)
    this.state = { hasError: false, error: null }
  }

  // Static Methods:Update Based on the Error state
  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error }
  }

  // Life Cycle:Performing side effects after catching an error(Log Reporting)
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('ErrorBoundary An error was caught:', error.message)
    console.error('Component Stack:', errorInfo.componentStack)

    // Reported to the error monitoring service
    if (this.props.onError) {
      this.props.onError(error, errorInfo)
    }

    // Can be integrated into actual projects Sentry:
    // Sentry.captureException(error, { extra: errorInfo })
  }

  handleReset = () => {
    this.setState({ hasError: false, error: null })
  }

  render() {
    if (this.state.hasError) {
      // Use Custom fallback Or default to a lower level UI
      if (this.props.fallback) {
        return this.props.fallback
      }

      return (
        <div
          role="alert"
          style={{
            padding: '32px 24px',
            margin: 16,
            background: '#fff2f0',
            border: '1px solid #ffccc7',
            borderRadius: 8,
            textAlign: 'center',
          }}
        >
          <h2 style={{ color: '#ff4d4f', margin: '0 0 12px' }}>
            A component error occurred
          </h2>
          <p style={{ color: '#666', marginBottom: 8, fontSize: 14 }}>
            {this.state.error?.message || 'An unknown error has occurred'}
          </p>
          <button
            onClick={this.handleReset}
            style={{
              padding: '6px 20px',
              background: '#ff4d4f',
              color: '#fff',
              border: 'none',
              borderRadius: 4,
              cursor: 'pointer',
              fontSize: 14,
            }}
          >
            Retry
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

export default ErrorBoundary
72 linhas de lógica (limite de 40, somente leitura)

Como usar o ErrorBoundary em projetos reais

TSX
// Layered Wrapping — Each independent area has its own ErrorBoundary
function OrderPage({ orderId }: { orderId: string }) {
  return (
    <div>
      {/* Order Header Information:It will display correctly even if there is an error below. */}
      <ErrorBoundary fallback={<p>Failed to load order</p>}>
        <OrderHeader orderId={orderId} />
      </ErrorBoundary>

      {/* Product List:Errors in one area do not affect other areas */}
      <ErrorBoundary
        onError={(err) => {
          // Rendering Error in the List of Submitted Products
          fetch('/api/log-error', {
            method: 'POST',
            body: JSON.stringify({ error: err.message, orderId }),
          })
        }}
      >
        <OrderItems orderId={orderId} />
      </ErrorBoundary>

      {/* Payment Information */}
      <ErrorBoundary>
        <PaymentInfo orderId={orderId} />
      </ErrorBoundary>
    </div>
  )
}

▶ Exemplo 2: O componente UserProfile com recuperação de erros

Em situações reais, às vezes não basta apenas exibir uma interface de usuário alternativa — os usuários podem precisar atualizar dados específicos. Aqui está um exemplo de como usar um limite de erro com um recurso de “Repetir”:

TSX 📖 Somente leitura
import { useState } from 'react'
import ErrorBoundary from './ErrorBoundary'

// Simulating Data Retrieval That Results in Errors
function fetchUserData(userId: number) {
  return fetch(`/api/users/${userId}`).then(res => {
    if (!res.ok) throw new Error('Failed to retrieve user data')
    return res.json()
  })
}

// Data display components that may have rendering errors
function UserInfo({ userId }: { userId: number }) {
  const [user, setUser] = useState<any>(null)
  const [loading, setLoading] = useState(true)

  useState(() => {
    fetchUserData(userId)
      .then(setUser)
      .finally(() => setLoading(false))
  })

  if (loading) return <p>Loading......</p>

  // If user Data Structure Exception,An error may occur here
  return (
    <div>
      <h3>{user.name}</h3>         {/* possibly:Cannot read properties of undefined */}
      <p>{user.profile.bio}</p>    {/* possibly:Cannot read properties of undefined */}
    </div>
  )
}

// Outer Container:With retries key Mechanism
function UserProfile({ userId }: { userId: number }) {
  const [retryKey, setRetryKey] = useState(0)

  return (
    <ErrorBoundary
      key={retryKey}  // Change key It will unmount and remount the subtree
      fallback={
        <div style={{ padding: 24, textAlign: 'center' }}>
          <p>Error loading user information</p>
          <button onClick={() => setRetryKey(k => k + 1)}>
            Retry Loading
          </button>
        </div>
      }
    >
      <UserInfo userId={userId} />
    </ErrorBoundary>
  )
}
42 linhas de lógica (limite de 40, somente leitura)

Dica importante: key={retryKey} Faça com que o ErrorBoundary desmonte e recrie sua subárvore quando uma nova tentativa for acionada, reiniciando assim o estado de todos os componentes filhos.


(2) Erros que não podem ser detectados por limites de erro

Os limites de erro não são uma panaceia; eles não conseguem detectar os quatro tipos de erros a seguir:

Tipo de erro Causa Solução
Erros no tratamento de eventos Os manipuladores de eventos não são executados durante a fase de renderização Coloque a lógica de tratamento de eventos em um bloco try/catch
Erros no código assíncrono As chamadas de retorno do setTimeout e do Promise não ocorrem dentro do ciclo de renderização do React Use try/catch ou Promise.catch
Erros na renderização do lado do servidor (SSR) Os limites de erro só surtem efeito no lado do cliente Envolva a renderização em blocos try/catch para SSR
Erro do próprio Error Boundary Ele lança um erro que não pode ser interceptado por ele mesmo Envolva-o em outro Error Boundary no nível mais externo

Tratamento de eventos + tratamento adequado de erros em código assíncrono

TSX
function PaymentForm() {
  async function handleSubmit() {
    try {
      const result = await submitPayment()
      // Processed successfully
    } catch (error) {
      // Asynchronous errors are caught here,Error Boundary That's none of my business
      console.error('Payment Failed:', error)
      // Display Error UI(For example, setting state)
      setError(error instanceof Error ? error.message : 'Payment Failed')
    }
  }

  // Errors in the event must also be used try/catch
  function handleClick() {
    try {
      processPayment()
    } catch (error) {
      setError('Processing Failed,Please try again.')
    }
  }
}

(3) Analisando o desempenho usando o Profiler do React DevTools

A guia “Profiler” no React DevTools é uma ferramenta essencial para analisar o desempenho da renderização de componentes. Ela gera um “gráfico de chamas” que ilustra visualmente o tempo de renderização de cada componente.

Instruções de uso

TEXT 📖 Somente leitura
1. Open your browser DevTools → Components Tabs
2. Switch to Profiler Sublabel
3. Click the blue record button(Start Recording)
4. Performing actions on the page(Click、Scrolling, etc.)
5. Click the Stop button(End Recording)
6. View the flame diagram

Como interpretar gráficos de chamas

TEXT 📖 Somente leitura
┌────────────────────────────────────────────┐
│  App (0.3ms)                               │
│  ├── Navbar (0.2ms)                        │
│  ├── OrderPage (2.1ms)                     │
│  │   ├── OrderHeader (0.4ms)  ── Gray     │
│  │   ├── OrderItems (1.5ms)   ── Yellow     │
│  │   │   └── OrderItem × 20 (each 0.3ms)     │
│  │   └── PaymentInfo (0.2ms) ── Gray      │
│  └── Footer (0.1ms)                        │
└────────────────────────────────────────────┘

▶ Exemplo 3: Medindo o tempo de renderização com o componente Profiler

O componente <Profiler> integrado ao React permite medir com precisão o tempo de renderização de um componente específico em seu código, tornando-o adequado para o monitoramento automatizado de métricas de desempenho:

TSX 📖 Somente leitura
import { Profiler } from 'react'

type ProfilerPhase = 'mount' | 'update' | 'nested-update'

interface ProfileMetrics {
  id: string
  phase: ProfilerPhase
  actualDuration: number      // Actual rendering time for this render(milliseconds)
  baseDuration: number        // Worst-case runtime for a subtree
  startTime: number           // Render Start Timestamp
  commitTime: number          // Submit to DOM timestamp
  interactions: Set<any>      // Related Interaction Tracking
}

// Performance Monitoring Callbacks
function onRenderCallback(
  id: string,
  phase: ProfilerPhase,
  actualDuration: number,
  baseDuration: number,
  startTime: number,
  commitTime: number,
) {
  // Record to the performance log
  if (actualDuration > 16) {  // More than 16ms = Frame drop threshold (60fps)
    console.warn(
      `[Performance Alerts] ${id} in  ${phase} Time Taken per Stage ${actualDuration.toFixed(1)}ms,` +
      `More than 16ms Frame Budget!`
    )

    // Reported to the performance monitoring system
    // reportPerformance({ id, phase, actualDuration, baseDuration })
  }

  // Output from the development environment to the console
  if (process.env.NODE_ENV === 'development') {
    console.table({
      'Components': id,
      'Phase': phase,
      'Actual time taken(ms)': actualDuration.toFixed(1),
      'Benchmark Duration(ms)': baseDuration.toFixed(1),
    })
  }
}

// Big Data List——Potential Performance Bottlenecks
function ProductList({ products }: { products: Product[] }) {
  return (
    <Profiler id="ProductList" onRender={onRenderCallback}>
      <div style={{ display: 'grid', gap: 16, gridTemplateColumns: 'repeat(3, 1fr)' }}>
        {products.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </Profiler>
  )
}
45 linhas de lógica (limite de 40, somente leitura)

Estratégias comuns de otimização de desempenho

TSX
// 1. React.memo — Avoid Unnecessary Re-rendering
const ProductCard = React.memo(function ProductCard({
  product,
}: {
  product: Product
}) {
  return (
    <div style={{ border: '1px solid #eee', padding: 16, borderRadius: 8 }}>
      <img src={product.image} alt={product.name} width="100%" />
      <h4>{product.name}</h4>
      <p>${product.price}</p>
    </div>
  )
})

// 2. useMemo — Cache the results of expensive computations
function OrderSummary({ items }: { items: OrderItem[] }) {
  const totalPrice = useMemo(() => {
    return items.reduce((sum, item) => {
      // Assuming that complex currency conversions were performed here
      return sum + convertCurrency(item.price, item.currency)
    }, 0)
  }, [items])

  return <p>Total:${totalPrice.toFixed(2)}</p>
}

// 3. useCallback — Stable function references
function OrderList({ orders, onSelect }: {
  orders: Order[]
  onSelect: (id: string) => void
}) {
  // ✅ use  useCallback Keep references consistent
  const handleSelect = useCallback((id: string) => {
    onSelect(id)
  }, [onSelect])

  return orders.map(order => (
    <OrderRow key={order.id} order={order} onSelect={handleSelect} />
  ))
}

(4) Depuração usando o painel de componentes do React DevTools

Além do Profiler, o painel “Componentes” do React DevTools também é uma ferramenta poderosa para a depuração no dia a dia:

Característica Finalidade Funcionamento
Navegar pela árvore de componentes Visualizar a hierarquia de componentes Clicar em DevTools → Componentes
Visualizar propriedades/estado em tempo real Verificar o estado atual do componente Selecionar um componente para visualizar o painel à direita
Modificar diretamente o estado Testar a interface do usuário em diferentes estados Clicar duas vezes em um valor de estado para editá-lo diretamente
Pesquisar componente Localização rápida de componente Ctrl+F Digite o nome do componente
Acessar o código-fonte Visualizar a implementação do componente Clicar no ícone <>
TEXT 📖 Somente leitura
// DevTools Components Panel Examples
<OrderPage>
  <ErrorBoundary>
    <OrderHeader
      orderNumber="ORD-2026-0001"     ← Props Real-time Display
      status="shipped"                 ← Can be edited directly during testing
    />
  </ErrorBoundary>
  <ErrorBoundary>
    <OrderItems>
      <OrderItem product={...} />      ← State Expand to view
      <OrderItem product={...} />
    </OrderItems>
  </ErrorBoundary>
</OrderPage>

▶ Exemplo 4: Como lidar com erros em solicitações de API — Recuperação de dados com mecanismos de repetição de tentativa

JSX
function useFetchWithRetry(url, maxRetries = 3) {
  const [data, setData] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)
  const [retries, setRetries] = useState(0)

  const fetchData = useCallback(async () => {
    setLoading(true)
    setError(null)
    try {
      const res = await fetch(url)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      const json = await res.json()
      setData(json)
    } catch (err) {
      if (retries < maxRetries) {
        setRetries(r => r + 1)
        setTimeout(fetchData, 1000 * (retries + 1))
      } else {
        setError(err.message)
      }
    } finally {
      setLoading(false)
    }
  }, [url, retries, maxRetries])

  useEffect(() => { fetchData() }, [url])

  return { data, error, loading, retries, refetch: () => { setRetries(0); fetchData() } }
}

function UserList() {
  const { data: users, error, loading, retries, refetch } = useFetchWithRetry('/api/users')

  if (loading) return <p>Loading... {retries > 0 && `(retry ${retries})`}</p>
  if (error) return (
    <div style={{ padding: 20, textAlign: 'center' }}>
      <p style={{ color: '#ff4d4f' }}>Error: {error}</p>
      <button onClick={refetch} style={{ padding: '8px 16px', cursor: 'pointer' }}>Retry</button>
    </div>
  )

  return (
    <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
  )
}
▶ Experimente

▶ Exemplo 5: Monitoramento global de erros — Integração com o Sentry

JSX
// lib/errorReporting.ts
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN

function initErrorReporting() {
  if (typeof window === 'undefined') return
  if (!SENTRY_DSN) return

  // Sentry.init({ dsn: SENTRY_DSN, ... })
  // Simplified Example:Simulation Using Global Event Listeners

  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled Promise:', event.reason)
    reportError({
      type: 'unhandledrejection',
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      timestamp: new Date().toISOString(),
    })
  })

  window.addEventListener('error', (event) => {
    console.error('Global Error:', event.error)
    reportError({
      type: 'window.error',
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      timestamp: new Date().toISOString(),
    })
  })
}

function reportError(payload) {
  fetch('/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  }).catch(() => {})
}

// app/layout.tsx
function RootLayout({ children }) {
  useEffect(() => { initErrorReporting() }, [])
  return <html><body>{children}</body></html>
}
▶ Experimente

❓ Perguntas Frequentes

P: O Error Boundary consegue detectar todos os erros? R: Não. O Error Boundary detecta apenas os erros que ocorrem durante a fase de renderização, nos métodos do ciclo de vida e nos construtores. Os quatro tipos de erros a seguir não podem ser capturados: erros em manipuladores de eventos (use try/catch), erros em código assíncrono (use Promise.catch), erros durante a renderização do lado do servidor e erros dentro do próprio Error Boundary. Ao projetar o tratamento de erros, é necessário combinar try/catch e os Error Boundaries em uma abordagem em camadas.

P: O que representam as cores “cinza” e “azul” no gráfico de chamas do Profiler? R: O cinza indica que o componente não foi renderizado novamente durante este commit (bom desempenho). O azul indica que o componente foi renderizado novamente, mas o tempo gasto ficou dentro da faixa normal. O amarelo ou o vermelho indicam que a renderização demorou muito e precisa de otimização. O objetivo é manter a maioria dos componentes em cinza ou azul claro durante as interações.

P: O React.memo otimiza automaticamente todos os componentes? R: Não. O React.memo realiza apenas comparações superficiais. Se os props contiverem objetos ou matrizes, cada renderização resultará em novas referências, fazendo com que o memo falhe. Nesse caso, você precisa usar useMemo / useCallback para manter as referências estáveis ou passar um segundo argumento — uma função de comparação personalizada React.memo(Comp, (prev, next) => deepEqual(prev, next)) — para o React.memo.

P: O componente <Profiler> afeta o desempenho em um ambiente de produção? R: De acordo com a documentação oficial do React, o componente <Profiler> causa uma ligeira sobrecarga de desempenho em um ambiente de produção. Recomenda-se usá-lo apenas em ambientes de desenvolvimento ou controlá-lo por meio de variáveis de ambiente: {process.env.NODE_ENV === 'development' && <Profiler>...}. Quando for necessário monitorar o desempenho em produção, considere o uso de bibliotecas dedicadas ao monitoramento de desempenho (como o web-vitals) ou os recursos de rastreamento de desempenho do Sentry.

P: É possível escrever um Error Boundary como um componente de função? R: Não neste momento. Os Error Boundaries dependem de dois métodos do ciclo de vida, getDerivedStateFromError e componentDidCatch, que são suportados apenas por componentes de classe. A equipe do React indicou que uma versão com Hooks poderá estar disponível no futuro, mas, por enquanto (React 18/19), eles só podem ser implementados usando componentes de classe. Você pode criar um componente de limite de erro baseado em classe e, em seguida, envolvê-lo em um componente de função para lidar com a lógica de recuperação de erros.


📖 Resumo


📝 Exercícios

  1. Crie um componente ErrorBoundary e utilize-o hierarquicamente dentro dos componentes OrderPage: Wrap OrderHeader, OrderItems, and PaymentInfo in separate ErrorBoundary. Provocar manualmente um erro de renderização (como passar props incorretos) para verificar se apenas a área afetada exibe a interface de fallback, enquanto o restante da interface é renderizado normalmente.
  2. Use o componente Profiler para medir o tempo de renderização de um componente que contenha 100 itens de lista. Após otimizar com React.memo, meça o tempo de renderização novamente e compare a diferença em actualDuration entre as duas medições para verificar a eficácia da otimização.
  3. Abra o Profiler do React DevTools no Chrome, registre uma interação na página (como uma pesquisa, um filtro ou uma ordenação), identifique o componente que demora mais para ser renderizado no gráfico de chamas, analise a causa e otimize-o usando useMemo / useCallback.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%