React: Noções básicas sobre estado: useState
Última atualização: 2026-08-26
O State é a memória de um componente. Se os Props são parâmetros fornecidos por terceiros (somente leitura), então o State é o seu próprio caderninho (editável). O useState é o “chip de memória” que o React instala em um componente.
1. O que você vai aprender
- O que é o “State” e por que ele é necessário?
- Noções básicas sobre o uso do useState
- A maneira correta de atualizar os estados de arrays e objetos (imutabilidade)
- Gerenciamento de múltiplas variáveis de estado
- Elevando o Estado
2. Reflexões suscitadas por um contador
(1) Problema: as variáveis regulares não acionam atualizações na interface do usuário
Bob quer criar um contador simples: quando você clica no botão, o número aumenta em 1.
// ❌ Ordinary Variables,Will not trigger a re-render
function Counter() {
let count = 0 // Ordinary Variables
function handleClick() {
count = count + 1 // Variable changed, but UI won't update!
console.log(count) // Console Display 1, 2, 3... But the page always displays 0
}
return (
<div>
<p>Count:{count}</p> {/* Always Show 0 */}
<button onClick={handleClick}>+1</button>
</div>
)
}
Bob descobriu um problema: As variáveis comuns não avisam o React para atualizar a interface do usuário. Toda vez que o componente é renderizado novamente, count é redefinido para 0.
(2) Uma solução utilizando useState
// ✅ Usage State,React It automatically tracks changes and updates UI
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0) // Initial value 0
function handleClick() {
setCount(count + 1) // Update Status → React Automatic Re-rendering
}
return (
<div>
<p>Count:{count}</p> {/* Automatically update with each click */}
<button onClick={handleClick}>+1</button>
</div>
)
}
Valor de retorno: useState retorna um array [current value, setter function]. Após chamar setCount, o React re-renderiza automaticamente o componente para exibir os valores mais recentes.
3. Conceitos fundamentais do useState
(1) Análise da tarefa
import { useState } from 'react'
// useState(initialValue) Back [value, setValue]
const [count, setCount] = useState(0)
// ^ ^ ^
// | | └─ Initial value(Applies only to the first render)
// | └─ Update Function(Triggers a re-render after being called)
// └─ Current status value(Get the latest value on every render)
| Seção | Descrição |
|---|---|
count |
Valor atual do estado; o valor mais recente é recuperado a cada renderização |
setCount |
Função de atualização: ao chamá-la → o React refaz a renderização do componente |
useState(0) |
Valor inicial (usado durante a primeira renderização; ignorado nas renderizações subsequentes) |
(2) Mecanismo de atualização
function Example() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1) // 1. Request Update
// Note:Here count Still the old value!
console.log(count) // Output 0,No 1
}
return <button onClick={handleClick}>{count}</button>
}
Conceito-chave:
setCount(count + 1)significa “solicitar ao React que atualizecountna próxima renderização”, e não “alterarcountimediatamente”. Isso é chamado de atualização assíncrona.
(3) Atualizações funcionais
Se o novo estado depender do antigo, você deve usar atualizações funcionais:
function Counter() {
const [count, setCount] = useState(0)
function handleClick() {
// ✅ Recommendations:Functional Updates
setCount(prev => prev + 1)
setCount(prev => prev + 1) // Called twice in a row, count becomes +2
}
// ❌ Not recommended:Update Directly(Chain calls take effect only once)
function handleBadClick() {
setCount(count + 1)
setCount(count + 1) // Called twice count All are old values,The result was only +1
}
return <button onClick={handleClick}>Currently:{count}</button>
}
| Método | Sintaxe | Características |
|---|---|---|
| Atualizar diretamente | setCount(count + 1) |
Simples, mas os dados são perdidos em chamadas consecutivas |
| Atualização funcional | setCount(prev => prev + 1) |
Preciso; funciona corretamente mesmo com chamadas consecutivas |
4. Atualização de matrizes e objetos (imutabilidade)
O React exige que o estado não seja modificado diretamente. Nunca use state.push() ou state.name = 'xxx'; em vez disso, crie um novo array ou objeto para substituir o valor antigo.
(1) Objetos a serem atualizados
function UserEditor() {
const [user, setUser] = useState({
name: 'Alice',
age: 28,
email: 'alice@example.com'
})
function updateName(newName) {
// ❌ Error:Edit directly state Object
user.name = newName
// React Changes will not be detected,UI Will not be updated
// ✅ Correct:Create a new object
setUser({ ...user, name: newName })
}
function updateAge(newAge) {
// ✅ Expand Operator:Keep the other fields,Update Only age
setUser({ ...user, age: newAge })
}
function resetUser() {
// ✅ Reset to default values
setUser({ name: '', age: 0, email: '' })
}
return (
<div>
<p>{user.name} - {user.age} years old</p>
<button onClick={() => updateName('Bob')}>Change Name Bob</button>
<button onClick={() => updateAge(user.age + 1)}>Age +1</button>
</div>
)
}
(2) Atualizar o array
function ShoppingCart() {
const [items, setItems] = useState([
{ id: 1, name: 'Apple', qty: 2 },
{ id: 2, name: 'Banana', qty: 1 }
])
// Add:Create a new array using the spread operator
function addItem(name) {
setItems([...items, { id: Date.now(), name, qty: 1 }])
}
// Delete:use filter Create a new array
function removeItem(id) {
setItems(items.filter(item => item.id !== id))
}
// Update:use map Create a new array
function updateQty(id, newQty) {
setItems(items.map(item =>
item.id === id ? { ...item, qty: newQty } : item
))
}
return (
<div>
<button onClick={() => addItem('Orange')}>Add orange</button>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} × {item.qty}
<button onClick={() => updateQty(item.id, item.qty + 1)}>+</button>
<button onClick={() => removeItem(item.id)}>Delete</button>
</li>
))}
</ul>
</div>
)
}
▶ Exemplo: Tabela de referência rápida para operações com matrizes
// ============================================
// Example:React Array Operations in C 6 Common Scenarios
// ============================================
const [arr, setArr] = useState([1, 2, 3])
// 1. Append to the end
setArr([...arr, 4]) // [1, 2, 3, 4]
// 2. Add to the beginning
setArr([0, ...arr]) // [0, 1, 2, 3]
// 3. Insert in the middle
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
// [1, 99, 2, 3]
// 4. Delete Element(filter)
setArr(arr.filter(n => n !== 2)) // [1, 3]
// 5. Update Element(map)
setArr(arr.map(n => n === 2 ? 22 : n)) // [1, 22, 3]
// 6. Sort(Copy first, then sort)
setArr([...arr].sort((a, b) => b - a)) // [3, 2, 1]
5. Várias variáveis de estado
Um componente pode ter várias variáveis de estado. Recomenda-se dividi-las por lógica, em vez de agrupá-las em um único objeto grande:
function RegistrationForm() {
// ✅ Recommendations:Logical Breakdown
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [errors, setErrors] = useState({})
// ❌ Not recommended:All states are stored in a single object
const [form, setForm] = useState({
name: '', email: '', password: '',
isSubmitting: false, errors: {}
})
// Expand the entire object when updating,Fields that are easy to overlook
}
| Estratégia | Vantagens | Desvantagens |
|---|---|---|
Dividir em vários useStates |
Atualizações precisas, seguras em termos de tipos e fáceis de ler | Muitas variáveis quando há muito estado |
Combinado em um único useState |
Prático para atualizar vários campos de uma só vez | A sintaxe de atualização é complicada e é fácil esquecer de expandi-la |
Recomendação: Use várias instâncias de
useStatepara estados independentes (como campos de entrada, indicadores de carregamento e mensagens de erro); campos logicamente relacionados (como todos os campos de um perfil de usuário) podem ser combinados em um único objeto.
6. Elevando o Estado
Quando vários componentes precisam compartilhar o mesmo estado, transfira o estado para o componente pai comum mais próximo.
graph TB
subgraph "Before the status upgrade(Data is not shared)"
A[Parent Component App] --- B[Child component A<br/>Have their own count]
A --- C[Child component B<br/>Have their own count]
end
subgraph "After the status upgrade(Data Sharing)"
D[Parent Component App<br/>**count Here**] --- E[Child component A<br/>Read count, Call setCount]
D --- F[Child component B<br/>Read count, Call setCount]
end
▶ Exemplo: Aplicação prática da promoção de estado
// ============================================
// Example:Temperature Converter(Status Upgrade)
// Features:Converting Between Celsius and Fahrenheit,Share the same temperature value
// ============================================
// ---- Child component:Celsius Temperature Input ----
function CelsiusInput({ celsius, onCelsiusChange }) {
return (
<div>
<label>Celsius(°C):</label>
<input
value={celsius}
onChange={e => onCelsiusChange(e.target.value)}
style={{ margin: '8px', padding: '4px' }}
/>
</div>
)
}
// ---- Child component:Fahrenheit Temperature Input ----
function FahrenheitInput({ fahrenheit, onFahrenheitChange }) {
return (
<div>
<label>Fahrenheit(°F):</label>
<input
value={fahrenheit}
onChange={e => onFahrenheitChange(e.target.value)}
style={{ margin: '8px', padding: '4px' }}
/>
</div>
)
}
// ---- Parent Component:Status is managed here ----
function TemperatureConverter() {
// State Propagated to the Common Parent Component
const [temperature, setTemperature] = useState('')
function handleCelsiusChange(value) {
setTemperature(value) // Store at Celsius
// There is no need for two states!Keep only one,Another one calculated using a formula
}
function handleFahrenheitChange(value) {
// Fahrenheit → Celsius:°C = (°F - 32) × 5/9
setTemperature(value ? ((parseFloat(value) - 32) * 5 / 9).toFixed(1) : '')
}
const celsius = temperature
const fahrenheit = temperature
? (parseFloat(temperature) * 9 / 5 + 32).toFixed(1)
: ''
return (
<div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h2>Temperature Converter</h2>
<CelsiusInput celsius={celsius} onCelsiusChange={handleCelsiusChange} />
<FahrenheitInput fahrenheit={fahrenheit} onFahrenheitChange={handleFahrenheitChange} />
{temperature && (
<p style={{ color: '#666', marginTop: '12px' }}>
{celsius}°C = {fahrenheit}°F
</p>
)}
</div>
)
}
// Enter in the Celsius input field 100 → Automatic Display in Fahrenheit 212°F
// Enter in the Fahrenheit input field 212 → Automatic Celsius Display 100°C
7. Exemplo completo: Contador do carrinho de compras
// ============================================
// Complete Example:Shopping Cart(useState Comprehensive Application)
// Features:Add Item、Increase or Decrease Quantity、Delete、Total Price Calculation
// ============================================
import { useState } from 'react'
function ShoppingCart() {
// Several State Variable,Logical Breakdown
const [items, setItems] = useState([
{ id: 1, name: 'React Hands-On Tutorials', price: 89, qty: 1 },
{ id: 2, name: 'TypeScript Getting Started', price: 59, qty: 2 }
])
const [discountCode, setDiscountCode] = useState('')
const [appliedDiscount, setAppliedDiscount] = useState(0)
// Increase the quantity(Functional updates ensure accuracy)
function increment(id) {
setItems(items.map(item =>
item.id === id ? { ...item, qty: item.qty + 1 } : item
))
}
// Decrease the quantity(No less than 1)
function decrement(id) {
setItems(items.map(item =>
item.id === id ? { ...item, qty: Math.max(1, item.qty - 1) } : item
))
}
// Delete Product
function remove(id) {
setItems(items.filter(item => item.id !== id))
}
// Apply a discount code
function applyDiscount() {
if (discountCode === 'REACT2026') {
setAppliedDiscount(20) // over 100 minus 20
} else {
alert('The discount code is invalid.')
}
}
// Calculate the Total Price
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
const total = Math.max(0, subtotal - appliedDiscount)
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<h2>🛒 Shopping Cart</h2>
{/* Product List */}
{items.length === 0 ? (
<p style={{ color: '#999', textAlign: 'center', padding: '40px' }}>
Your shopping cart is empty,Go check it out!!
</p>
) : (
items.map(item => (
<div key={item.id} style={{
display: 'flex', alignItems: 'center',
padding: '12px', borderBottom: '1px solid #f0f0f0'
}}>
<div style={{ flex: 1 }}>
<h4 style={{ margin: 0 }}>{item.name}</h4>
<p style={{ margin: '4px 0', color: '#ff4d4f' }}>${item.price}</p>
</div>
{/* Quantity Control */}
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<button onClick={() => decrement(item.id)} style={btnStyle}>−</button>
<span>{item.qty}</span>
<button onClick={() => increment(item.id)} style={btnStyle}>+</button>
</div>
{/* Subtotal */}
<p style={{ margin: '0 16px', fontWeight: 'bold', width: '80px', textAlign: 'right' }}>
${item.price * item.qty}
</p>
{/* Delete */}
<button onClick={() => remove(item.id)} style={{ ...btnStyle, backgroundColor: '#ff4d4f', color: 'white' }}>
✕
</button>
</div>
))
)}
{/* Discount Code */}
<div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
<input
value={discountCode}
onChange={e => setDiscountCode(e.target.value)}
placeholder="Enter the discount code"
style={{ flex: 1, padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
/>
<button onClick={applyDiscount} style={{
padding: '8px 16px', backgroundColor: '#52c41a', color: 'white',
border: 'none', borderRadius: '4px', cursor: 'pointer'
}}>
Applications
</button>
</div>
{/* Price Summary */}
<div style={{ marginTop: '16px', padding: '16px', backgroundColor: '#fafafa', borderRadius: '8px' }}>
<p>Subtotal:${subtotal}</p>
{appliedDiscount > 0 && <p style={{ color: '#52c41a' }}>Discount: -${appliedDiscount}</p>}
<p style={{ fontSize: '20px', fontWeight: 'bold' }}>Total:${total}</p>
</div>
</div>
)
}
const btnStyle = {
width: '32px', height: '32px',
border: '1px solid #d9d9d9', borderRadius: '4px',
backgroundColor: 'white', cursor: 'pointer',
fontSize: '16px', display: 'flex', alignItems: 'center',
justifyContent: 'center'
}
Fluxo de interação:
- Toque em +/− para ajustar a quantidade; o subtotal e o total são atualizados em tempo real
- Clique em ✕ para excluir um item; exiba uma mensagem amigável quando o carrinho de compras estiver vazio
- Digite o código promocional
REACT2026para economizar 20 yuan
▶ Exemplo 3: Atualizando o estado de um objeto — Editando informações do usuário
function UserEditor() {
const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })
function updateField(field, value) {
setUser(prev => ({ ...prev, [field]: value }))
}
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Edit Profile</h3>
<input value={user.name} onChange={e => updateField('name', e.target.value)}
placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<input value={user.email} onChange={e => updateField('email', e.target.value)}
placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
{JSON.stringify(user, null, 2)}
</pre>
</div>
)
}
▶ Exemplo 4: Estados de objetos aninhados — Gerenciamento de endereços
function AddressForm() {
const [address, setAddress] = useState({
street: '', city: '', zip: '',
country: 'US', isPrimary: true,
})
function update(path, value) {
setAddress(prev => ({ ...prev, [path]: value }))
}
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Shipping Address</h3>
<input value={address.street} onChange={e => update('street', e.target.value)}
placeholder="Street" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<input value={address.city} onChange={e => update('city', e.target.value)}
placeholder="City" style={{ flex: 2, padding: 8, borderRadius: 4 }} />
<input value={address.zip} onChange={e => update('zip', e.target.value)}
placeholder="ZIP" style={{ flex: 1, padding: 8, borderRadius: 4 }} />
</div>
<select value={address.country} onChange={e => update('country', e.target.value)}
style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }}>
<option value="US">United States</option>
<option value="CN">China</option>
<option value="JP">Japan</option>
</select>
<label>
<input type="checkbox" checked={address.isPrimary}
onChange={e => update('isPrimary', e.target.checked)} /> Primary address
</label>
</div>
)
}
▶ Exemplo 5: As atualizações funcionais resolvem o problema das atualizações em lote
function ScoreBoard() {
const [score, setScore] = useState(0)
const [multiplier, setMultiplier] = useState(1)
function addPoints(base) {
setScore(prev => prev + base * multiplier)
}
function resetScore() {
setScore(0)
setMultiplier(1)
}
function doubleMultiplier() {
setMultiplier(prev => Math.min(prev * 2, 8))
}
return (
<div style={{ maxWidth: 300, margin: '0 auto', textAlign: 'center' }}>
<h3>Score: {score}</h3>
<p>Multiplier: x{multiplier}</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 8 }}>
<button onClick={() => addPoints(10)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+10 pts</button>
<button onClick={() => addPoints(50)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+50 pts</button>
<button onClick={() => addPoints(100)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+100 pts</button>
</div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
<button onClick={doubleMultiplier} style={{ padding: '8px 16px', cursor: 'pointer' }}>2x Multiplier</button>
<button onClick={resetScore} style={{ padding: '8px 16px', cursor: 'pointer' }}>Reset</button>
</div>
</div>
)
}
❓ Perguntas Frequentes
P: Por que
countainda exibe o valor antigo quando impresso imediatamente após a chamada desetCount? R: PorquesetCounté uma “solicitação de atualização”, e não uma “atualização imediata”. O React atualiza o estado em lotes depois que todos os manipuladores de eventos são executados e, em seguida, aciona uma nova renderização. O novo valor decountsó estará disponível na próxima renderização. Isso é chamado de atualização assíncrona.
P: O que acontece se eu modificar diretamente o objeto de estado (user.name = 'xxx')? R: Nenhum erro será gerado, mas a interface do usuário não será atualizada. O React determina se deve re-renderizar comparando as referências aos estados antigo e novo. Modificar diretamente as propriedades de um objeto não altera a referência; portanto, o React considera-o “inalterado”. Portanto, você deve criar um novo objeto:
setUser({...user, name: 'xxx'}).
P: Qual é a diferença de desempenho entre
useStatee as variáveis comuns? R:useStateacarreta uma sobrecarga adicional (rastreamento de alterações, agendamento de renderização), portanto, só deve ser usada em cenários em que “as atualizações da interface do usuário são acionadas quando ocorrem alterações”. Se uma variável for alterada, mas não exigir uma atualização da interface do usuário (como um ID de temporizador ou a posição de rolagem), é mais adequado usaruseRef(Lição 10).
P: Quantos
useStates podem existir em um único componente? Há algum problema de desempenho? R: Não há limite para o número. A documentação oficial do React recomenda dividir os elementos em váriosuseStates com base na lógica, em vez de usar um único objeto grande. Cada chamada deuseStateé uma “unidade de estado”, e o React consegue lidar com eficiência com um grande número deuseStates. Componentes comuns geralmente têm de 3 a 8useStates.
P: A atualização do
setStateé síncrona ou assíncrona? R: Antes do React 18, osetStateera “agrupado automaticamente” dentro de manipuladores de eventos, mas era síncrono nas chamadas dosetTimeoute doPromise. O React 18 introduziu o Agrupamento Automático, de modo que setState agora é agrupado de forma assíncrona, independentemente de ser chamado dentro de um manipulador de eventos, setTimeout, Promise ou callback de fetch. Se você precisar absolutamente acessar o DOM atualizado de forma síncrona, pode usarflushSync(() => setState(...)).
📖 Resumo
- O estado é a “memória” de um componente; quando ele muda, isso aciona automaticamente uma atualização da interface do usuário
useState(initialValue)Retorna a matriz[value, setValue]- Imutabilidade: Nunca modifique o estado diretamente; em vez disso, crie um novo objeto ou array para substituí-lo.
- A atualização funcional
setCount(prev => prev + 1)é mais precisa quando se baseia em valores antigos - Elevação de estado: Quando vários componentes filhos precisam compartilhar dados, o estado é elevado para um componente pai comum.
📝 Exercícios
- Exercício básico (Dificuldade ⭐): Crie um componente
LikeButtonque alterne entre os estados ❤️ e 🤍 quando clicado e exiba o número de curtidas. - Exercício avançado (Dificuldade ⭐⭐): Crie um componente
ExpenseTrackerque utilizeuseStatepara gerenciar uma lista de despesas (descrição, valor, data) e permita adicionar e remover itens. - Desafio (Dificuldade: ⭐⭐⭐): Crie um componente
ColorPickerque utilize três instâncias deuseStatepara gerenciar os valores dos controles deslizantes R, G e B (0–255) e exiba uma pré-visualização da cor em tempo real e o código hexadecimal da cor.