React: useEffect e o ciclo de vida
Última atualização: 2026-08-26
Se
useStateé a “memória” de um componente, entãouseEffecté sua “câmera” — ela permite que você faça algumas coisas adicionais depois que o componente já foi renderizado na tela (“depois de tirar a foto”): solicitar dados, assinar eventos, manipular o DOM e definir temporizadores.
1. O que você vai aprender
- O objetivo e o uso básico do
useEffect - Três padrões para o uso de matrizes
- Funções de limpeza (para evitar vazamentos de memória)
- A relação entre
useEffecte o ciclo de vida do componente - useEffect x useLayoutEffect
2. A história de uma página de carregamento de dados
(1) Problema: As solicitações de dados causam um laço infinito
Alice quer solicitar os dados do usuário quando o componente for carregado, então ela escreve o seguinte:
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null)
// ❌ Error:Requesting data directly within a component's function body
fetch('/api/users/' + userId)
.then(res => res.json())
.then(data => setUser(data))
return <div>{user?.name}</div>
}
Pergunta:
fetch→setUser(data)→ Reexibição do componente- Renderizar novamente → Executar
fetchnovamente → Definir o status novamente → Renderizar novamente... - Loop infinito! A página está travada.
Alice precisa de uma maneira de “executar apenas uma vez quando o componente for montado”.
(2) Solução com useEffect
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
// ✅ Correct:useEffect + Empty array of dependencies
useEffect(() => {
fetch('/api/users/' + userId)
.then(res => {
if (!res.ok) throw new Error('Failed to load')
return res.json()
})
.then(data => {
setUser(data)
setLoading(false)
})
.catch(err => {
setError(err.message)
setLoading(false)
})
}, [userId]) // Only at userId Re-execute when changes occur
if (loading) return <p>Loading......</p>
if (error) return <p>Error:{error}</p>
return <h1>{user.name}</h1>
}
Benefícios: useEffect Separa os efeitos colaterais da lógica de renderização — a renderização é responsável apenas pela interface do usuário, e os efeitos colaterais são executados após a conclusão da renderização.
3. Noções básicas sobre o useEffect
(1) Sintaxe básica
useEffect(() => {
// Side Effect Logic(Execute after rendering is complete)
console.log('Component rendering complete!')
return () => {
// Cleanup Function(Run before unloading or re-executing a component)
console.log('Addressing Side Effects')
}
}, [Dependency Array])
| Seção | Descrição |
|---|---|
() => { ... } |
Efeito colateral da chamada de retorno, executada após a renderização ter sido finalizada na tela |
return () => { ... } |
Função de limpeza; é executada antes da reexecução devido à desinstalação de um componente ou a alterações nas dependências |
[dependencies] |
Um array que controla quando o useEffect é executado novamente |
graph TB
A[Component Rendering] --> B[React Submit DOM Update]
B --> C[useEffect Callback Execution]
C --> D{Changes in Dependencies?}
D -->|is | E[Execute the previous cleanup function]
E --> F[Re-execute useEffect]
D -->|No| G[Skip]
F --> H{Component Uninstallation?}
G --> H
H -->|is | I[Execute the cleanup function]
style C fill:#61dafb,color:#000
style I fill:#ff6b6b,color:#fff
(2) Três padrões para matrizes de dependências
// Pattern 1:Empty array [] — Execute only once when mounted
useEffect(() => {
console.log('Component Mounting')
fetch('/api/init').then(setData)
}, []) // Never re-execute
// Pattern 2:Dependencies [a, b] — Re-execute when dependencies change
useEffect(() => {
console.log('userId It has changed.:', userId)
fetch('/api/users/' + userId).then(setUser)
}, [userId]) // userId Re-execute when changes occur
// Pattern 3:No array dependencies — Executed on every render
useEffect(() => {
console.log('Executed on every render')
}) // Note:No array!
| Conjunto de dependências | Tempo de execução | Casos de uso |
|---|---|---|
[] |
Executado apenas uma vez ao montar | Inicializar solicitações de dados e assinaturas |
[dep1, dep2] |
Ao ser montado + quando as dependências mudam | Os dados mudam com os parâmetros |
| Inúmeros conjuntos | Após cada renderização | Raramente usado (geralmente substituído por outros métodos) |
4. Funções de limpeza
As funções de limpeza são fundamentais para evitar vazamentos de memória e condições de corrida.
(1) Zerar o cronômetro
function Timer() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
// ✅ Clear the timer when the component is unloaded
return () => {
clearInterval(timer)
console.log('The timer has been cleared.')
}
}, []) // Start only when mounted
return <p>Running: {seconds}s</p>
}
// If you don't clean it,:The timer continues to run even after the component is unloaded,Error "Update on unmounted components"
(2) Limpeza dos ouvintes de eventos
function WindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
function handleResize() {
setSize({ width: window.innerWidth, height: window.innerHeight })
}
window.addEventListener('resize', handleResize)
// ✅ Remove event listeners when the component is unmounted
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return <p>Window:{size.width} × {size.height}</p>
}
(3) Cancelamento da solicitação (para evitar condições de corrida)
function SearchResults({ query }) {
const [results, setResults] = useState([])
useEffect(() => {
let cancelled = false
fetch('/api/search?q=' + query)
.then(res => res.json())
.then(data => {
// ✅ If the component has been uninstalled or query Has changed,Do not update status
if (!cancelled) {
setResults(data)
}
})
// ✅ Cleanup:Uncheck
return () => {
cancelled = true
}
}, [query])
return <div>{results.map(r => <p key={r.id}>{r.name}</p>)}</div>
}
// When users quickly switch search terms:
// 1. Search"A" → useEffect Execute
// 2. Search"AB" → Clear Function Markers cancelled=true → useEffect Re-execute
// 3. Search"A" the response came back → cancelled=true → Do not update status(Avoid flashing)
▶ Exemplo: Demonstração abrangente das funções de limpeza
// ============================================
// Example:useEffect Declaration Cycle Demonstration
// Features:Start the timer when mounted + Listening for Keyboard Events
// Clean up all resources upon uninstallation
// ============================================
function LifecycleDemo() {
const [count, setCount] = useState(0)
const [logs, setLogs] = useState([])
// Effect 1:Title Update
useEffect(() => {
document.title = `Count:${count}`
}, [count])
// Effect 2:Timer
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1)
}, 2000)
addLog('⏱️ The timer has started')
return () => {
clearInterval(id)
addLog('⏱️ The timer has been cleared.')
}
}, [])
// Effect 3:Keyboard Events
useEffect(() => {
function handleKey(e) {
if (e.key === ' ') {
setCount(c => c + 5)
}
}
window.addEventListener('keydown', handleKey)
return () => {
window.removeEventListener('keydown', handleKey)
addLog('⌨️ Keyboard listening has been removed')
}
}, [])
function addLog(msg) {
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()} ${msg}`])
}
return (
<div>
<h2>useEffect Life Cycle Demonstration</h2>
<p>Count:{count} (+1 every 2s, Space +5)</p>
<div style={{ maxHeight: '200px', overflow: 'auto', backgroundColor: '#f5f5f5', padding: '10px', borderRadius: '4px', fontSize: '12px', fontFamily: 'monospace' }}>
{logs.map((log, i) => <div key={i}>{log}</div>)}
</div>
</div>
)
}
5. Mapeando o useEffect ao ciclo de vida do componente
Na era dos componentes de classe, havia três ganchos de ciclo de vida; na era dos Hooks, useEffect — um único gancho faz o trabalho de três:
graph LR
subgraph "The Lifecycle of Class Components"
A1[componentDidMount] --> A2[componentDidUpdate]
A2 --> A3[componentWillUnmount]
end
subgraph "Hooks 's useEffect"
B1["useEffect(fn, [])<br/>Execute when mounted"]
B2["useEffect(fn, [dep])<br/>Mount + run when deps change"]
B3["return cleanup<br/>Clean up before uninstalling or rerunning"]
end
| Ciclo de vida dos componentes de classe | useEffect correspondente |
|---|---|
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate |
useEffect(() => { ... }) (inúmeros conjuntos) ou useEffect(() => { ... }, [dep]) |
Funções de limpeza em componentWillUnmount |
useEffect(() => { return () => { ... } }, []) |
▶ Exemplo: Como usar um único useEffect para lidar com várias fases do ciclo de vida
// ============================================
// Example:Chat Room Component——One useEffect Covering the Entire Lifecycle
// ============================================
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([])
useEffect(() => {
// === componentDidMount + componentDidUpdate ===
console.log(`🟢 Connect to the chat room:${roomId}`)
const connection = createConnection(roomId)
connection.connect()
connection.on('message', (msg) => {
setMessages(prev => [...prev, msg])
})
// === componentWillUnmount + Clean Up Before Re-Execution ===
return () => {
console.log(`🔴 Leave the chat room:${roomId}`)
connection.disconnect()
}
}, [roomId]) // roomId When things change:Disconnect the old connection → Join a New Chat Room
return (
<div>
<h3>Chat Room:{roomId}</h3>
{messages.map((msg, i) => <p key={i}>{msg}</p>}
</div>
)
}
// Usage
<ChatRoom roomId="general" /> // Connect general Chat Room
<ChatRoom roomId="random" /> // roomId Changes → Disconnect general → Connect random
// When a component is unloaded → Disconnect the current connection(Execution of the cleanup function)
6. useEffect x useLayoutEffect
O React oferece dois hooks com “efeitos colaterais”, que diferem em termos de tempo de execução.
graph LR
A[Trigger Rendering] --> B[Computational Virtual DOM]
B --> C[DOM Update]
C --> D{useLayoutEffect?}
D -->|is | E[Execute in parallel<br/>Blocking Browser Rendering]
D -->|No| F[Browser rendering complete]
F --> G[useEffect Asynchronous Execution]
style E fill:#faad14,color:#000
style G fill:#52c41a,color:#fff
| Hook | Tempo de execução | Bloqueia a renderização | Casos de uso |
|---|---|---|---|
useEffect |
Após a renderização do navegador (assíncrona) | ❌ | 90% dos cenários: solicitações de dados, ouvintes de eventos, registro em log |
useLayoutEffect |
Após as atualizações do DOM, antes da renderização do navegador (síncrono) | ✅ | Medir as dimensões do DOM, ler o layout, sincronizar animações |
import { useState, useEffect, useLayoutEffect } from 'react'
function MeasureExample() {
const [width, setWidth] = useState(0)
const ref = useRef(null)
// ❌ useEffect Read DOM Dimensions → Users may notice flickering
useEffect(() => {
setWidth(ref.current.offsetWidth) // Read After Drawing → May trigger additional rendering
}, [])
// ✅ useLayoutEffect → Read Before Drawing,Transparent to the user
useLayoutEffect(() => {
setWidth(ref.current.offsetWidth)
}, [])
return <div ref={ref}>Width:{width}px</div>
}
Regras: Comece digitando
useEffect; só mude parauseLayoutEffectse encontrar problemas de “cintilação” ou “instabilidade no layout”.
7. Exemplo completo: Painel de dados
// ============================================
// Complete Example:Data Dashboard(useEffect Integrated Applications)
// Features:Refresh data at regular intervals + Window Adaptation + Resource Cleanup
// ============================================
import { useState, useEffect } from 'react'
function Dashboard() {
const [data, setData] = useState(null)
const [isLive, setIsLive] = useState(true)
const [lastUpdate, setLastUpdate] = useState(null)
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
})
// Effect 1:First Load + Refresh at set intervals
useEffect(() => {
function fetchData() {
// Simulation API Request
const mockData = {
users: Math.floor(Math.random() * 1000) + 500,
orders: Math.floor(Math.random() * 200) + 100,
revenue: Math.floor(Math.random() * 50000) + 10000,
growth: (Math.random() * 20 - 5).toFixed(1)
}
setData(mockData)
setLastUpdate(new Date().toLocaleTimeString())
}
// Execute once immediately
fetchData()
// Refresh at set intervals
let interval
if (isLive) {
interval = setInterval(fetchData, 5000)
}
return () => {
clearInterval(interval)
}
}, [isLive]) // isLive Reset the timer when a change occurs
// Effect 2:Window Size Monitoring
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// Effect 3:Page Title
useEffect(() => {
document.title = `Dashboard - ${data ? `${data.users} User` : 'Loading......'}`
}, [data])
// Effect 4:Leave Page Prompt
useEffect(() => {
function handleBeforeUnload(e) {
if (isLive) {
e.preventDefault()
e.returnValue = ''
}
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
}, [isLive])
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
<h2>📊 Real-Time Dashboard</h2>
<div>
<span style={{ color: '#666', fontSize: '14px', marginRight: '12px' }}>
Window:{windowSize.width}×{windowSize.height}
</span>
<label>
<input type="checkbox" checked={isLive} onChange={e => setIsLive(e.target.checked)} />
Refreshes in real time
</label>
</div>
</div>
{!data ? (
<p style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
⏳ Loading......
</p>
) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
<StatCard label="Total Users" value={data.users.toLocaleString()} color="#1890ff" />
<StatCard label="Today's Orders" value={data.orders.toLocaleString()} color="#52c41a" />
<StatCard label="Monthly Income" value={`$${data.revenue.toLocaleString()}`} color="#ff4d4f" />
<StatCard label="Growth Rate" value={`${data.growth}%`} color={parseFloat(data.growth) >= 0 ? '#52c41a' : '#ff4d4f'} />
</div>
)}
<p style={{ textAlign: 'right', color: '#999', fontSize: '12px', marginTop: '12px' }}>
Last Updated:{lastUpdate} {isLive ? '(Auto-refresh every 5s)' : '(Suspended)'}
</p>
</div>
)
}
function StatCard({ label, value, color }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
padding: '20px',
textAlign: 'center',
boxShadow: '0 2px 4px rgba(0,0,0,0.05)'
}}>
<p style={{ color: '#666', margin: '0 0 8px 0' }}>{label}</p>
<p style={{ fontSize: '28px', fontWeight: 'bold', color, margin: 0 }}>{value}</p>
</div>
)
}
Resultado esperado: Um painel de dados contendo 4 cartões de estatísticas, um botão para ativar/desativar a atualização em tempo real, um indicador do tamanho da janela e a hora da última atualização.
▶ Exemplo 3: Utilização do useEffect para implementar a sincronização de dados em tempo real
function LiveSearch({ apiBase }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!query.trim()) { setResults([]); return }
setLoading(true)
const controller = new AbortController()
fetch(`${apiBase}/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
.then(res => res.json())
.then(data => { setResults(data); setLoading(false) })
.catch(err => { if (err.name !== 'AbortError') setLoading(false) })
return () => controller.abort()
}, [query, apiBase])
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search users..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
{loading && <p>Searching...</p>}
<ul style={{ listStyle: 'none', padding: 0 }}>
{results.map(r => <li key={r.id} style={{ padding: 4, borderBottom: '1px solid #f0f0f0' }}>{r.name}</li>)}
</ul>
</div>
)
}
▶ Exemplo 4: useEffect para monitorar alterações no tamanho da janela
function useWindowSize() {
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
useEffect(() => {
function handleResize() {
setSize({ width: window.innerWidth, height: window.innerHeight })
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return size
}
function ResponsiveLayout() {
const { width } = useWindowSize()
const layout = width < 640 ? 'mobile' : width < 1024 ? 'tablet' : 'desktop'
return (
<div style={{ padding: 16, maxWidth: 600, margin: '0 auto' }}>
<h3>Viewport: {width}px ({layout})</h3>
{layout === 'mobile' && <p>📱 Single column layout</p>}
{layout === 'tablet' && <p>📟 Two column layout</p>}
{layout === 'desktop' && <p>💻 Three column layout with sidebar</p>}
</div>
)
}
▶ Exemplo 5: useEffect + localStorage para persistir o estado
function usePersistedState(key, initial) {
const [value, setValue] = useState(() => {
try {
const saved = localStorage.getItem(key)
return saved !== null ? JSON.parse(saved) : initial
} catch { return initial }
})
useEffect(() => {
try { localStorage.setItem(key, JSON.stringify(value)) }
catch (e) { console.error('Save failed:', e) }
}, [key, value])
return [value, setValue]
}
function TodoApp() {
const [todos, setTodos] = usePersistedState('todos', [])
const [input, setInput] = useState('')
function addTodo() {
if (!input.trim()) return
setTodos(prev => [...prev, { id: Date.now(), text: input.trim(), done: false }])
setInput('')
}
function toggleTodo(id) {
setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t))
}
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Todos (persisted)</h3>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={input} onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && addTodo()}
placeholder="Add task..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
<button onClick={addTodo} style={{ padding: '8px 16px', cursor: 'pointer' }}>Add</button>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map(t => (
<li key={t.id} onClick={() => toggleTodo(t.id)}
style={{ padding: 4, cursor: 'pointer', textDecoration: t.done ? 'line-through' : 'none', color: t.done ? '#999' : '#333' }}>
{t.text}
</li>
))}
</ul>
</div>
)
}
❓ Perguntas Frequentes
P: Qual é a diferença entre
useEffecte escrever código diretamente dentro de um componente? R: O código dentro deuseEffecté executado somente após a conclusão da renderização, portanto não bloqueia a interface do usuário. O código escrito diretamente no corpo da função de um componente é executado durante a renderização (o que bloqueia a renderização) e é executado a cada renderização. A principal diferença é queuseEffectpermite que você execute uma limpeza quando o componente é desmontado.
P: Por que a função de limpeza em
useEffecté executada antes de cada nova execução? R: Essa é uma garantia de design do React — ela evita “condições de corrida” causadas por alterações nas dependências. Por exemplo, quando ouserIdé alterado, a solicitação antiga é limpa antes que uma nova seja iniciada, garantindo que os novos dados substituam os antigos. Se a nova solicitação substituísse a antiga diretamente, poderia ocorrer um bug em que “a solicitação antiga fosse concluída primeiro → os dados fossem revertidos”.
P: O que devo fazer se o ESLint relatar o erro
React Hook useEffect has a missing dependency? R: Não use// eslint-disable-next-linepara ignorá-lo! Isso significa que seuuseEffectestá sem dependências, o que pode causar bugs. A abordagem correta é: ① Adicione as dependências que faltam à matriz; ② Se você realmente não precisa reagir a alterações nessas dependências, isso indica um problema de design — considere encapsular a lógica interna emuseCallbackou usar uma ref.
P: Qual é a ordem de execução de vários ganchos
useEffect? R: Eles são executados na ordem em que aparecem no código. Portanto, você deve colocar os hooksuseEffectque lidam com a “inicialização” (como o carregamento de dados) primeiro, e aqueles que lidam com a “resposta a alterações” (como a sincronização do estado comlocalStorage) por último. Durante uma única renderização, todos os hooksuseEffectsão executados em sequência.
P: Qual é a diferença entre
useEffecteuseLayoutEffect? R: A diferença está no momento em que são executados:useEffecté executado de forma assíncrona após a atualização do DOM (após a renderização do navegador), de modo que os usuários não percebam nenhuma oscilação;useLayoutEffecté executado de forma síncrona após a atualização do DOM (antes da renderização do navegador), permitindo que você modifique o DOM e, em seguida, faça com que o navegador o renderize. UseuseEffectem 99% dos casos. UseuseLayoutEffectapenas quando você “precisar fazer cálculos com base no layout do DOM e, em seguida, modificar o DOM de forma síncrona” (como ajustar a posição de um elemento após medir suas dimensões).
📖 Resumo
useEffect(fn, deps)Executar efeitos colaterais após a conclusão da renderização, sem bloquear a interface do usuário- Dependência vazia
[]= Executada apenas uma vez durante a montagem; Dependência não vazia = Executada durante a montagem e quando as dependências são alteradas - Funções de limpeza
return () => {}são fundamentais para evitar vazamentos de memória — temporizadores, ouvintes de eventos e assinaturas devem ser todos removidos - Use
useEffectem 90% dos casos; useuseLayoutEffectapenas ao medir o DOM. - Vários ganchos
useEffectsão executados na ordem em que estão escritos
📝 Exercícios
- Problema básico (Dificuldade ⭐): Crie um componente
PageTitle. UseuseEffectpara alterar o título da página para “Bem-vindo ao meu site” quando o componente for montado e restaurá-lo ao título original quando for desmontado. - Exercício avançado (Dificuldade ⭐⭐): Crie um componente
OnlineStatusque utilizeuseEffectpara monitorar eventos provenientes denavigator.onLineeonline/offlinee exiba o status da rede em tempo real. - Desafio (Dificuldade: ⭐⭐⭐): Crie um hook personalizado chamado
usePollingque aceite os parâmetrosurleinterval, faça a consulta à interface uma vez a cada intervalo de milissegundos e retorne{ data, isLoading, error }. O hook deve permitir pausar e retomar a consulta.