React: Tratamento de eventos
Última atualização: 2026-08-26
O tratamento de eventos é como as terminações nervosas de um aplicativo React — quando os usuários clicam em botões, digitam texto ou enviam formulários, essas interações são respondidas por meio de eventos. O sistema de eventos do React é mais inteligente e mais unificado do que os eventos nativos do DOM.
1. O que você vai aprender
- A diferença entre eventos compostos do React e eventos nativos Eventos comuns: onClick / onChange / onSubmit / onKeyDown
- Tratamento de objetos de evento
- Passagem de parâmetros para funções de manipulador de eventos
- Impedir o comportamento padrão e a propagação de eventos
2. Uma história sobre a interação com formulários
(1) Pontos críticos: 3 armadilhas no envio de formulários
Bob está criando um formulário de cadastro de usuários e escrevendo manipuladores de eventos em JavaScript nativo:
// Native JS:Various compatibility issues and details need to be handled manually
// 1. Get DOM Element
const form = document.getElementById('register-form')
const nameInput = document.getElementById('name')
const submitBtn = document.getElementById('submit-btn')
// 2. Event Binding
form.addEventListener('submit', function(event) {
event.preventDefault() // Prevent the page from refreshing
// 3. Manually Retrieving Form Data
const formData = new FormData(form)
const data = Object.fromEntries(formData)
// 4. Submit Data
fetch('/api/register', {
method: 'POST',
body: JSON.stringify(data)
})
})
// 5. Real-Time Input Validation
nameInput.addEventListener('input', function(event) {
if (event.target.value.length < 2) {
showError('Name (at least) 2 characters')
} else {
hideError()
}
})
// 6. Prevent duplicate submissions when the Submit button is clicked
let isSubmitting = false
submitBtn.addEventListener('click', function() {
if (isSubmitting) return
isSubmitting = true
submitBtn.disabled = true
submitBtn.textContent = 'Submitting......'
})
O problema do Bob:
- Código disperso: a vinculação de eventos, o processamento de dados e as atualizações da interface do usuário estão espalhados por diferentes locais
- Vazamento de memória: Esquecer de remover os ouvintes de eventos
- Problemas de compatibilidade: Existem diferenças entre as APIs de eventos dos diversos navegadores
(2) Tratamento de eventos no React
function RegisterForm() {
const [name, setName] = React.useState('')
const [isSubmitting, setIsSubmitting] = React.useState(false)
// Submit for Processing
function handleSubmit(event) {
event.preventDefault()
setIsSubmitting(true)
fetch('/api/register', {
method: 'POST',
body: JSON.stringify({ name })
}).finally(() => setIsSubmitting(false))
}
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Please enter your name"
/>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting......' : 'Register'}
</button>
</form>
)
}
Benefícios: O código foi reduzido de 30 linhas para 15 linhas, sem manipulação do DOM, sem problemas de desvinculação de eventos e sem problemas de compatibilidade com navegadores.
3. Eventos compostos do React
Os eventos do React não são eventos nativos do DOM, mas sim SyntheticEvents — o React os monitora no nível superior e simula um sistema de eventos compatível com todos os navegadores.
| Recurso | Eventos nativos do DOM | Eventos compostos do React |
|---|---|---|
| Método de vinculação | addEventListener / onclick |
Escrever diretamente em JSX |
| Compatibilidade com navegadores | Necessidade de tratamento manual das diferenças | Padronização automática |
| Gerenciamento de memória | Os ouvintes devem ser removidos manualmente | Limpeza automática |
| Objeto de evento | Evento nativo | Evento sintético |
| Evitar a formação de bolhas | event.stopPropagation() |
Idêntico (padronizado) |
| Bloqueio padrão | event.preventDefault() |
Igual (padronizado) |
graph TB
A[The user clicks the button] --> B[React Root Node Event Handling]
B --> C[Create a Synthetic Event Object]
C --> D[Simulated Bubbles/Capture Phase]
D --> E[Call JSX Bound to the middle handler]
style B fill:#61dafb,color:#000
style C fill:#1890ff,color:#fff
4. Referência rápida para eventos comuns
| Nome do evento | Condições de acionamento | Cenários comuns | Tipo de objeto do evento |
|---|---|---|---|
onClick |
Elemento clicado | Botão, link, cartão | MouseEvent |
onChange |
Alterações no conteúdo dos campos de entrada | Campos de formulário, menus suspensos | ChangeEvent |
onSubmit |
Enviar formulário | Entrar/Cadastre-se/Pesquisar | FormEvent |
onFocus |
O elemento recebe o foco | O campo de entrada está destacado | FocusEvent |
onBlur |
O elemento perde o foco | Validação de entrada | FocusEvent |
onKeyDown |
Pressione uma tecla | Tecla de atalho, pressione Enter para pesquisar | KeyboardEvent |
onKeyUp |
Imprensa e Comunicados | Pesquisa em tempo real | KeyboardEvent |
onMouseEnter |
Efeito ao passar o mouse | MouseEvent |
|
onMouseLeave |
Efeito ao sair com o mouse | Efeito ao passar o mouse | MouseEvent |
onScroll |
Rolagem | Rolagem infinita | UIEvent |
▶ Exemplo: Visão geral abrangente de eventos comuns
// ============================================
// Example:Complete Event Handling for a Login Form
// ============================================
function LoginForm() {
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const [errors, setErrors] = React.useState({})
const [focusedField, setFocusedField] = React.useState('')
function handleSubmit(event) {
event.preventDefault()
// Form Validation
const newErrors = {}
if (!email.includes('@')) newErrors.email = 'The email address format is incorrect.'
if (password.length < 6) newErrors.password = 'Password must be at least 6 chars'
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
// Submit Login
console.log('Log In:', { email, password })
}
function handleKeyDown(event) {
// Press Escape key to clear focus
if (event.key === 'Escape') {
event.target.blur()
}
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
<h2>Log In</h2>
{/* Enter your email address */}
<div style={{ marginBottom: '16px' }}>
<label>Email:</label>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
onFocus={() => setFocusedField('email')}
onBlur={() => { setFocusedField(''); setErrors({...errors, email: undefined}) }}
onKeyDown={handleKeyDown}
style={{
width: '100%',
padding: '8px',
borderColor: errors.email ? '#ff4d4f' : focusedField === 'email' ? '#1890ff' : '#d9d9d9'
}}
/>
{errors.email && <p style={{ color: '#ff4d4f', fontSize: '12px' }}>{errors.email}</p>}
</div>
{/* Password Entry */}
<div style={{ marginBottom: '16px' }}>
<label>Password:</label>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
onFocus={() => setFocusedField('password')}
onBlur={() => setFocusedField('')}
onKeyDown={handleKeyDown}
style={{
width: '100%',
padding: '8px',
borderColor: errors.password ? '#ff4d4f' : focusedField === 'password' ? '#1890ff' : '#d9d9d9'
}}
/>
{errors.password && <p style={{ color: '#ff4d4f', fontSize: '12px' }}>{errors.password}</p>}
</div>
{/* Submit Button */}
<button
type="submit"
onClick={() => console.log('Clicked the "Log In" button')}
onMouseEnter={() => console.log('Button on Mouse Hover')}
onMouseLeave={() => console.log('Mouse leaves button')}
style={{
width: '100%',
padding: '10px',
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Log In
</button>
</form>
)
}
5. Passagem de parâmetros em eventos
(1) Passagem direta de parâmetros
Envolva-o em uma função-seta e passe os parâmetros adicionais diretamente:
function UserList({ users, onDelete }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name}
{/* Arrow Functions:Incoming user.id As an additional parameter */}
<button onClick={() => onDelete(user.id)}>
Delete
</button>
</li>
))}
</ul>
)
}
// Usage
<UserList
users={users}
onDelete={(id) => console.log('Delete User:', id)}
/>
(2) Recuperar tanto o objeto do evento quanto os parâmetros personalizados
function ColorPicker({ colors, onSelect }) {
return (
<div>
<p>Select a Color:</p>
{colors.map(color => (
<button
key={color}
onClick={(event) => {
// event:Native Synthesis Event Object
// color:Custom Parameters
onSelect(color)
console.log('Click here:', event.clientX, event.clientY)
}}
style={{
backgroundColor: color,
width: '40px',
height: '40px',
border: '2px solid #ddd',
borderRadius: '50%',
margin: '4px',
cursor: 'pointer'
}}
/>
))}
</div>
)
}
(3) Três maneiras de passar objetos de evento
| Método | Sintaxe | Cenários aplicáveis |
|---|---|---|
| Passagem implícita | onClick={handleClick} |
Não são necessários parâmetros adicionais |
| Funções-seta | onClick={() => handleClick(id)} |
Requer parâmetros |
| Passar simultaneamente | onClick={(e) => handleClick(e, id)} |
Requer um objeto de evento + parâmetros personalizados |
6. Como evitar o comportamento padrão e a propagação
(1) Impedir o comportamento padrão: preventDefault()
O caso de uso mais comum é impedir que a página seja atualizada quando um formulário é enviado:
function SearchForm() {
const [query, setQuery] = React.useState('')
function handleSubmit(event) {
event.preventDefault() // Prevent the page from refreshing
// Execute Custom Search Logic
console.log('Search:', query)
}
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search..."
/>
<button type="submit">Search</button>
</form>
)
}
// Other scenarios where the default behavior needs to be overridden:
// - Link Redirection:<a href="#" onClick={e => e.preventDefault()}>
// - Context Menu:onContextMenu={e => e.preventDefault()}
// - Drag-and-Drop File Upload:onDragOver={e => e.preventDefault()}
(2) Evitar a formação de bolhas: stopPropagation()
function Modal() {
return (
// Click the mask layer to close the pop-up window
<div
style={overlayStyle}
onClick={() => console.log('Click the mask layer → Close Pop-up')}
>
{/* Clicking on the pop-up content does not display a speech bubble */}
<div
style={modalStyle}
onClick={(event) => {
event.stopPropagation() // Prevent Bubbles from Rising to the Mask Layer
console.log('Click on the pop-up window content')
}}
>
<h2>Pop-up Title</h2>
<p>Pop-up Content</p>
<button onClick={() => console.log('Close')}>Close</button>
</div>
</div>
)
}
// Click "Modal Content" area → only triggers modal onClick
// Click "Overlay" (outside modal) → trigger mask layer onClick
7. Exemplo completo: Lista de tarefas (tratamento abrangente de eventos)
// ============================================
// Example:Todo List(Comprehensive Incident Handling)
// Features:Add a Task、Marked as complete、Delete Task、Double-click to edit
// ============================================
function TodoApp() {
const [todos, setTodos] = React.useState([
{ id: 1, text: 'Study React Event', done: false },
{ id: 2, text: 'Complete the homework', done: false }
])
const [input, setInput] = React.useState('')
// 1. Add a Task(onSubmit + Enter key)
function handleSubmit(event) {
event.preventDefault()
if (!input.trim()) return
setTodos([...todos, {
id: Date.now(),
text: input.trim(),
done: false
}])
setInput('')
}
// 2. Quick Add with the Enter Key(onKeyDown)
function handleKeyDown(event) {
if (event.key === 'Enter' && input.trim()) {
handleSubmit(event)
}
}
// 3. Switch to "Completed" status(onChange)
function handleToggle(id) {
setTodos(todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
))
}
// 4. Delete Task(onClick + Parameter Passing)
function handleDelete(id, event) {
event.stopPropagation() // Prevent triggering li the incident
setTodos(todos.filter(t => t.id !== id))
}
// 5. Double-click to edit(onDoubleClick)
function handleEdit(todo) {
const newText = prompt('Edit Task:', todo.text)
if (newText && newText.trim()) {
setTodos(todos.map(t =>
t.id === todo.id ? { ...t, text: newText.trim() } : t
))
}
}
return (
<div style={{ maxWidth: '500px', margin: '0 auto' }}>
<h2>📋 Todo List</h2>
{/* Input Form */}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Input Task,Press Enter to add..."
style={{
width: '70%', padding: '8px',
border: '1px solid #d9d9d9', borderRadius: '4px'
}}
/>
<button type="submit" style={{
padding: '8px 16px', marginLeft: '8px',
backgroundColor: '#1890ff', color: 'white',
border: 'none', borderRadius: '4px', cursor: 'pointer'
}}>
Add
</button>
</form>
{/* Statistics */}
<p style={{ color: '#666', fontSize: '14px' }}>
Total {todos.length} items, Completed {todos.filter(t => t.done).length} items
</p>
{/* Task List */}
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map(todo => (
<li
key={todo.id}
onDoubleClick={() => handleEdit(todo)}
style={{
display: 'flex', alignItems: 'center',
padding: '10px', margin: '4px 0',
backgroundColor: todo.done ? '#f6ffed' : '#fff',
border: '1px solid #f0f0f0',
borderRadius: '4px',
textDecoration: todo.done ? 'line-through' : 'none',
color: todo.done ? '#999' : '#333',
cursor: 'pointer'
}}
>
{/* Checkbox */}
<input
type="checkbox"
checked={todo.done}
onChange={() => handleToggle(todo.id)}
style={{ marginRight: '10px' }}
/>
{/* Mission Text */}
<span style={{ flex: 1 }}>{todo.text}</span>
{/* Delete Button */}
<button
onClick={(e) => handleDelete(todo.id, e)}
style={{
padding: '2px 8px',
backgroundColor: 'transparent',
color: '#ff4d4f',
border: 'none',
cursor: 'pointer',
fontSize: '16px'
}}
>
✕
</button>
</li>
))}
</ul>
</div>
)
}
Fluxo de interação:
- Digite o nome da tarefa na caixa de texto, pressione Enter ou clique no botão “Adicionar” → A tarefa é adicionada à lista
- Marque a caixa de seleção → Marque a tarefa como concluída (o texto fica riscado e em cinza)
- Clique no botão ✕ → Exclua a tarefa (
stopPropagationpara evitar que um clique duplo acione a função de edição)- Clique duas vezes em qualquer tarefa → É exibida uma caixa de diálogo para edição
▶ Exemplo 2: Atalhos de teclado e gerenciamento do foco
// ============================================
// Example:Shortcut Keys Panel——Integrated Use of Keyboard Events and Focus Management
// Features:Usage onKeyDown Implement Keyboard Shortcuts,Usage onFocus/onBlur Management Focus
// ============================================
function ShortcutPanel() {
const [output, setOutput] = React.useState('')
const [activeKeys, setActiveKeys] = React.useState(new Set())
const inputRef = React.useRef(null)
// Keyboard Event Handling
function handleKeyDown(event) {
const { key, ctrlKey, shiftKey, altKey } = event
// Ctrl+S:Save
if (ctrlKey && key === 's') {
event.preventDefault()
setOutput('💾 Saved(Ctrl+S)')
}
// Ctrl+Z:Revoke
else if (ctrlKey && key === 'z') {
event.preventDefault()
setOutput('↩️ Undo(Ctrl+Z)')
}
// Escape:Clear Output
else if (key === 'Escape') {
setOutput('')
event.target.blur()
}
// Enter:Confirm
else if (key === 'Enter') {
setOutput(`✅ Confirm:${event.target.value || '(Empty input)'}`)
}
// Display the currently pressed key
setActiveKeys(prev => new Set([...prev, key]))
}
function handleKeyUp(event) {
setActiveKeys(prev => {
const next = new Set(prev)
next.delete(event.key)
return next
})
}
// Autofocus
React.useEffect(() => {
inputRef.current.focus()
}, [])
return (
<div style={{ maxWidth: '500px', margin: '0 auto' }}>
<h2>⌨️ Shortcut Keys Panel</h2>
<input
ref={inputRef}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onFocus={() => setOutput('The input field is currently focused')}
onBlur={() => setOutput('The input field has lost focus')}
placeholder="Type here,Try the shortcut keys..."
style={{
width: '100%',
padding: '10px',
fontSize: '16px',
border: '2px solid #1890ff',
borderRadius: '6px',
outline: 'none'
}}
/>
{/* Keyboard Shortcut Tips */}
<div style={{ marginTop: '12px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{[
{ key: 'Ctrl+S', desc: 'Save' },
{ key: 'Ctrl+Z', desc: 'Revoke' },
{ key: 'Enter', desc: 'Confirm' },
{ key: 'Escape', desc: 'Clear/blur' }
].map(shortcut => (
<span key={shortcut.key} style={{
padding: '4px 10px',
backgroundColor: '#f5f5f5',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '12px',
fontFamily: 'monospace'
}}>
{shortcut.key} <span style={{ color: '#999' }}>{shortcut.desc}</span>
</span>
))}
</div>
{/* Currently Pressed Key */}
{activeKeys.size > 0 && (
<p style={{ marginTop: '8px', fontSize: '13px', color: '#666' }}>
Currently Pressed Key:{[...activeKeys].join(' + ')}
</p>
)}
{/* Operation Output */}
{output && (
<div style={{
marginTop: '12px',
padding: '10px',
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '4px',
color: '#52c41a',
fontWeight: 'bold'
}}>
{output}
</div>
)}
</div>
)
}
▶ Exemplo 3: Tratamento de eventos de classificação por arrastar e soltar
function DragSortList() {
const [items, setItems] = useState(['Apple', 'Banana', 'Cherry', 'Date'])
const [dragIdx, setDragIdx] = useState(null)
function handleDragStart(idx) { setDragIdx(idx) }
function handleDragOver(e, idx) {
e.preventDefault()
if (dragIdx === null || dragIdx === idx) return
setItems(prev => {
const updated = [...prev]
const [moved] = updated.splice(dragIdx, 1)
updated.splice(idx, 0, moved)
return updated
})
setDragIdx(idx)
}
function handleDragEnd() { setDragIdx(null) }
return (
<div style={{ maxWidth: 300, margin: '0 auto' }}>
<h3>Drag to Reorder</h3>
{items.map((item, idx) => (
<div key={item} draggable
onDragStart={() => handleDragStart(idx)}
onDragOver={e => handleDragOver(e, idx)}
onDragEnd={handleDragEnd}
style={{
padding: '8px 12px', marginBottom: 4, borderRadius: 4, cursor: 'grab',
background: dragIdx === idx ? '#e6f7ff' : '#f5f5f5',
border: dragIdx === idx ? '2px solid #1890ff' : '2px solid transparent',
}}>
{item}
</div>
))}
</div>
)
}
▶ Exemplo 4: Hook personalizado para encapsular ouvintes de eventos
function useEventListener(event, handler, element = window) {
useEffect(() => {
element.addEventListener(event, handler)
return () => element.removeEventListener(event, handler)
}, [event, handler, element])
}
function MouseTracker() {
const [pos, setPos] = useState({ x: 0, y: 0 })
const handler = useCallback(e => setPos({ x: e.clientX, y: e.clientY }), [])
useEventListener('mousemove', handler)
return (
<div style={{ padding: 20 }}>
<p>Mouse: ({pos.x}, {pos.y})</p>
<div style={{
width: 200, height: 200, border: '1px solid #ddd', position: 'relative', borderRadius: 4,
}}>
<div style={{
width: 10, height: 10, borderRadius: '50%', background: '#1890ff',
position: 'absolute', left: Math.min(pos.x - 100, 190), top: Math.min(pos.y - 100, 190),
transition: 'left 0.1s, top 0.1s',
}} />
</div>
</div>
)
}
▶ Exemplo 5: Envio de formulário e combinação de eventos
function SearchForm({ onSearch }) {
const [query, setQuery] = useState('')
const [category, setCategory] = useState('all')
function handleSubmit(e) {
e.preventDefault()
onSearch({ query, category })
}
function handleReset() {
setQuery('')
setCategory('all')
}
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={query} onChange={e => setQuery(e.target.value)}
placeholder="Search..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
<select value={category} onChange={e => setCategory(e.target.value)}
style={{ padding: 8, borderRadius: 4 }}>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button type="submit" style={{ padding: '6px 16px', cursor: 'pointer' }}>Search</button>
<button type="button" onClick={handleReset} style={{ padding: '6px 16px', cursor: 'pointer' }}>Reset</button>
</div>
</form>
)
}
❓ Perguntas Frequentes
P: Por que usar uma função-seta
() => handleClick(id)em vez de simplesmentehandleClick(id)? R: Se você escreveronClick={handleClick(id)}diretamente, o React executaráhandleClick(id)imediatamente durante a renderização, em vez de esperar até que seja clicado. Isso ocorre porque se trata de uma chamada de função (com parênteses), e não de uma referência à função (sem parênteses). A abordagem correta éonClick={() => handleClick(id)}(função-seta com execução adiada) ouonClick={handleClick}(referenciando diretamente o nome da função quando nenhum argumento é passado).
P: Os objetos de evento do React são objetos
Eventnativos? Por que algumas propriedades aparecem comonullquando as registro no console? R: Os objetos de evento compostos do React são reciclados e reinicializados após a conclusão da chamada de retorno do evento (mecanismo de pool). Se você precisar acessar as propriedades do objeto de evento de forma assíncrona (como emsetTimeoutoufetch.then), deve primeiro chamarevent.persist()ou armazenar as propriedades necessárias em uma variável. Embora o pooling tenha sido descontinuado a partir do React 17+, ainda é recomendável seguir essa prática recomendada.
P: Quando o
onChangeé acionado no React? Ele é diferente doonChangenativo? R: Sim, é diferente. O<input>onchangenativo é acionado apenas quando o campo de entrada perde o foco; oonChangedo React é acionado imediatamente sempre que o conteúdo do campo de entrada é alterado (equivalente aooninputnativo). O React padroniza o comportamento de diferentes elementos de formulário, proporcionando uma experiência de desenvolvimento mais consistente.
P: É possível associar vários eventos idênticos a um único elemento? R: Não é possível associar diretamente dois
onClick. Soluções: ① Chamar várias funçõesonClick={() => { fn1(); fn2() }}dentro do mesmo manipulador; ② Ou envolver o elemento com vários HOCs. Normalmente, um elemento precisa apenas de um manipulador de eventos, dentro do qual é possível chamar vários trechos de lógica.
P: Qual é a diferença entre os eventos sintéticos do React e os eventos nativos? R: O
SyntheticEventdo React é um wrapper compatível com vários navegadores que envolve os eventos nativos, fornecendo uma API unificada (comoe.preventDefault()ee.stopPropagation()) e se comportando de maneira consistente em todos os navegadores. Os eventos sintéticos operam sob um mecanismo de delegação de eventos — todos os eventos são anexados ao nó raiz, em vez de a elementos DOM individuais. No React 17 e versões posteriores, os eventos são delegados à raiz, em vez de aodocument, o que evita conflitos quando várias versões do React coexistem.
📖 Resumo
- O React usa o SyntheticEvent, que garante compatibilidade entre navegadores e limpeza automática
- Eventos comuns: onClick, onChange, onSubmit, onKeyDown, onFocus/onBlur
- Use funções-seta para passar parâmetros adicionais
onClick={() => handler(id)} event.preventDefault()Impedir o comportamento padrão,event.stopPropagation()Impedir a propagação- Não digite
onClick={handler(id)}(isso será executado imediatamente); em vez disso, useonClick={() => handler(id)}.
📝 Exercícios
- Exercício básico (Dificuldade ⭐): Crie um componente
ButtonCounterque incremente o valor exibido em 1 cada vez que o botão for clicado. Use o eventoonClick. - Exercício avançado (Dificuldade ⭐⭐): Crie um componente
SearchInputque implemente a “busca com supressão de rebote”: a busca é acionada automaticamente 500 ms após o usuário parar de digitar. UseonChangeeuseEffect. - Desafio (Dificuldade: ⭐⭐⭐): Crie um componente
DragAndDropListque implemente a classificação por arrastar e soltar. Utilize os eventosonDragStart,onDragOvereonDrop. Os itens da lista podem ser arrastados para novas posições.