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
- Os princípios e as melhores práticas relativos aos limites de erro
- Como analisar gráficos de chama no Profiler do React DevTools
- O componente Profiler mede o tempo de renderização dos componentes
- Estratégias de diagnóstico e resolução para problemas comuns de desempenho
2. Diagramas conceituais
A figura abaixo ilustra o processo de captura do Limite de Erro e a cadeia de análise do Profiler:
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:
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
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
Como usar o ErrorBoundary em projetos reais
// 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”:
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>
)
}
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
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
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
┌────────────────────────────────────────────┐
│ 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) │
└────────────────────────────────────────────┘
- Cinza: Sem nova renderização (cenário ideal)
- Azul: Foi renderizado novamente, mas o tempo de processamento ficou normal
- Amarelo/Vermelho: A renderização demora muito; requer atenção
▶ 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:
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>
)
}
Estratégias comuns de otimização de desempenho
// 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 <> |
// 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
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>
)
}
▶ Exemplo 5: Monitoramento global de erros — Integração com o Sentry
// 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>
}
❓ 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 (usePromise.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 combinartry/catche 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.memootimiza 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 usaruseMemo/useCallbackpara manter as referências estáveis ou passar um segundo argumento — uma função de comparação personalizadaReact.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,
getDerivedStateFromErrorecomponentDidCatch, 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
- O Error Boundary é a solução declarativa de gerenciamento de erros do React, que evita que uma única falha faça com que todo o aplicativo exiba uma tela em branco.
- O Error Boundary só pode ser implementado utilizando componentes de classe, por meio da colaboração dos métodos de ciclo de vida
getDerivedStateFromErrorecomponentDidCatch. - Alterar o
keyno Limite de Erro permite reinicializar (remontar) a subárvore. - O Error Boundary não consegue detectar erros em manipuladores de eventos, código assíncrono, SSR ou dentro de si mesmo
- O Profiler do React DevTools exibe os tempos de renderização por meio de um gráfico de chamas; os componentes em amarelo e vermelho são aqueles que precisam de otimização.
- React.memo, useMemo e useCallback são a “trindade” da otimização de desempenho do React
📝 Exercícios
- Crie um componente
ErrorBoundarye utilize-o hierarquicamente dentro dos componentesOrderPage: WrapOrderHeader,OrderItems, andPaymentInfoin separateErrorBoundary. 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. - Use o componente
Profilerpara medir o tempo de renderização de um componente que contenha 100 itens de lista. Após otimizar comReact.memo, meça o tempo de renderização novamente e compare a diferença emactualDurationentre as duas medições para verificar a eficácia da otimização. - 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.