状态是组件内部的可变数据,当状态变化时,组件会自动重新渲染以反映最新的数据。
在React 16.8之前,状态管理只能通过Class组件实现。Hooks的引入使得函数组件也能拥有状态管理能力,现在成为推荐的方式。
useState是React提供的一个Hook,用于在函数组件中添加状态管理。
import React, { useState } from 'react';
function Example() {
// 声明一个名为count的state变量,初始值为0
// setCount是更新count的函数
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
点击增加
</button>
</div>
);
}
| 部分 | 名称 | 作用 | 说明 |
|---|---|---|---|
count |
状态变量 | 存储当前状态值 | 组件重新渲染时会保持最新的值 |
setCount |
更新函数 | 更新状态值并触发重新渲染 | 调用此函数时,组件会重新渲染 |
useState(initialValue)设置初始状态值
用户交互或事件触发setState(newValue)调用
React更新内部状态值,并计划重新渲染
组件函数重新执行,返回新的JSX,界面更新
用户看到更新后的界面,新的状态值被使用
import React, { useState } from 'react';
function Counter() {
// 声明count状态变量,初始值为0
const [count, setCount] = useState(0);
// 增加计数器
const increment = () => {
setCount(count + 1);
};
// 减少计数器
const decrement = () => {
setCount(count - 1);
};
// 重置计数器
const reset = () => {
setCount(0);
};
// 翻倍计数器
const double = () => {
setCount(count * 2);
};
return (
<div className="counter">
<h2>计数器演示</h2>
<div className="counter-value">{count}</div>
<div className="counter-controls">
<button onClick={decrement} className="btn btn-danger">
<i className="fas fa-minus"></i> 减少
</button>
<button onClick={reset} className="btn btn-secondary">
<i className="fas fa-redo"></i> 重置
</button>
<button onClick={increment} className="btn btn-success">
<i className="fas fa-plus"></i> 增加
</button>
</div>
<button onClick={double} className="btn btn-outline-primary">
<i className="fas fa-times"></i> 翻倍
</button>
</div>
);
}
function BasicState() {
// 字符串状态
const [name, setName] = useState('React');
// 数字状态
const [age, setAge] = useState(18);
// 布尔状态
const [isActive, setIsActive] = useState(true);
// 数组状态
const [items, setItems] = useState(['苹果', '香蕉', '橙子']);
return (
<div>
<p>名称: {name}</p>
<p>年龄: {age}</p>
<p>状态: {isActive ? '活跃' : '非活跃'}</p>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
function UserProfile() {
// 对象状态
const [user, setUser] = useState({
name: '张三',
age: 28,
email: 'zhangsan@example.com',
isVerified: false
});
// 更新对象状态 - 正确方式(展开运算符)
const updateEmail = (newEmail) => {
setUser({
...user, // 复制原有属性
email: newEmail // 更新email属性
});
};
// 更新对象状态 - 错误方式(直接修改)
const wrongUpdate = () => {
// ❌ 错误:直接修改原对象
user.age = 29;
setUser(user); // 不会触发重新渲染!
};
// 切换验证状态
const toggleVerification = () => {
setUser({
...user,
isVerified: !user.isVerified
});
};
return (
<div className="user-profile">
<h3>用户信息</h3>
<p>姓名: {user.name}</p>
<p>年龄: {user.age}</p>
<p>邮箱: {user.email}</p>
<p>验证状态: {user.isVerified ? '✓ 已认证' : '未认证'}</p>
<button onClick={() => updateEmail('new@example.com')}>
更新邮箱
</button>
<button onClick={toggleVerification}>
切换验证状态
</button>
</div>
);
}
React状态更新必须保持不可变性,即不直接修改原状态,而是创建新状态对象。这是因为React通过对比新旧状态来判断是否需要重新渲染。
错误: state.property = newValue
正确: setState({...state, property: newValue})
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习React', completed: true },
{ id: 2, text: '学习Hooks', completed: false },
{ id: 3, text: '构建项目', completed: false }
]);
// 添加新的待办事项
const addTodo = () => {
const newTodo = {
id: Date.now(),
text: '新任务',
completed: false
};
setTodos([...todos, newTodo]); // 创建新数组
};
// 删除待办事项
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id)); // 过滤出剩余项
};
// 切换完成状态
const toggleComplete = (id) => {
setTodos(
todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed } // 创建新对象
: todo
)
);
};
return (
<div className="todo-list">
<h3>待办事项 ({todos.length})</h3>
<button onClick={addTodo} className="btn btn-primary">
添加任务
</button>
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleComplete(todo.id)}
/>
<span>{todo.text}</span>
<button
onClick={() => deleteTodo(todo.id)}
className="btn btn-sm btn-danger"
>
删除
</button>
</li>
))}
</ul>
</div>
);
}
当新状态依赖于旧状态时,应该使用函数式更新,避免状态更新不同步的问题。
function CounterAdvanced() {
const [count, setCount] = useState(0);
// ❌ 可能有问题的方式(快速点击时)
const incrementTwice = () => {
setCount(count + 1); // 第一次更新
setCount(count + 1); // 仍然使用旧的count值
};
// ✅ 正确的方式:使用函数式更新
const incrementTwiceCorrectly = () => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1); // 基于最新状态
};
// 复杂计算示例
const complexUpdate = () => {
setCount(prev => {
// 基于前一个值进行计算
const newValue = prev * 2 + 5;
console.log(`从 ${prev} 更新到 ${newValue}`);
return newValue;
});
};
return (
<div>
<p>计数: {count}</p>
<button onClick={incrementTwice}>
增加两次(有问题)
</button>
<button onClick={incrementTwiceCorrectly}>
增加两次(正确)
</button>
<button onClick={complexUpdate}>
复杂更新
</button>
</div>
);
}
如果初始状态需要通过复杂计算得到,可以使用函数作为useState的参数。
function ExpensiveComponent() {
// ❌ 每次渲染都会执行复杂计算
// const [data, setData] = useState(expensiveCalculation());
// ✅ 只有初始渲染时执行一次
const [data, setData] = useState(() => {
// 这个函数只在组件首次渲染时执行
console.log('执行复杂计算...');
return expensiveCalculation();
});
// 模拟复杂计算
function expensiveCalculation() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i % 2;
}
return result;
}
return <div>计算结果: {data}</div>
}
function UserForm() {
// 分开的状态变量 - 推荐
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
const [isSubscribed, setIsSubscribed] = useState(false);
// 合并的状态变量 - 适用于相关状态
const [formData, setFormData] = useState({
name: '',
email: '',
age: 0,
isSubscribed: false
});
// 处理表单提交
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单数据:', { name, email, age, isSubscribed });
};
// 重置表单(分开状态)
const resetForm = () => {
setName('');
setEmail('');
setAge(0);
setIsSubscribed(false);
};
return (
<form onSubmit={handleSubmit} className="user-form">
<div className="form-group">
<label>姓名:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="form-group">
<label>邮箱:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="form-group">
<label>年龄:</label>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
/>
</div>
<div className="form-group">
<label>
<input
type="checkbox"
checked={isSubscribed}
onChange={(e) => setIsSubscribed(e.target.checked)}
/>
订阅新闻
</label>
</div>
<button type="submit">提交</button>
<button type="button" onClick={resetForm}>重置</button>
</form>
);
}
React的状态更新是异步的,这意味着调用setState后,状态不会立即更新。React会批量处理多个状态更新以提高性能。
const [count, setCount] = useState(0);
// 错误:立即读取更新后的值
setCount(10);
console.log(count); // 仍然是0,不是10
// 正确:使用useEffect监听状态变化
useEffect(() => {
console.log('count已更新:', count);
}, [count]);
使用函数式更新确保基于最新的状态值进行计算:
// 问题:快速点击时可能不会增加2
const incrementTwice = () => {
setCount(count + 1);
setCount(count + 1); // 仍然使用旧的count
};
// 解决方案:使用函数式更新
const incrementTwiceCorrectly = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 基于最新的prev值
};
当多个组件需要共享同一状态时,应该将状态提升到它们最近的共同父组件中:
// 父组件管理共享状态
function Parent() {
const [sharedState, setSharedState] = useState('共享数据');
return (
<div>
<ChildA value={sharedState} onChange={setSharedState} />
<ChildB value={sharedState} onChange={setSharedState} />
</div>
);
}
[user, setUser])
import React, { useState } from 'react';
function ShoppingCart() {
// 商品列表状态
const [products, setProducts] = useState([
{ id: 1, name: 'iPhone 14', price: 6999, quantity: 1 },
{ id: 2, name: 'MacBook Pro', price: 12999, quantity: 1 },
{ id: 3, name: 'AirPods Pro', price: 1999, quantity: 2 }
]);
// 折扣状态
const [discount, setDiscount] = useState(0);
const [hasDiscount, setHasDiscount] = useState(false);
// 计算总价
const calculateTotal = () => {
const subtotal = products.reduce(
(total, product) => total + product.price * product.quantity,
0
);
return subtotal - discount;
};
// 更新商品数量
const updateQuantity = (id, newQuantity) => {
if (newQuantity < 1) return;
setProducts(
products.map(product =>
product.id === id
? { ...product, quantity: newQuantity }
: product
)
);
};
// 移除商品
const removeProduct = (id) => {
setProducts(products.filter(product => product.id !== id));
};
// 应用折扣
const applyDiscount = (percentage) => {
const subtotal = products.reduce(
(total, product) => total + product.price * product.quantity,
0
);
setDiscount(subtotal * (percentage / 100));
setHasDiscount(true);
};
// 重置折扣
const resetDiscount = () => {
setDiscount(0);
setHasDiscount(false);
};
return (
<div className="shopping-cart">
<h2>购物车</h2>
<div className="cart-items">
{products.map(product => (
<div key={product.id} className="cart-item">
<div className="item-info">
<h4>{product.name}</h4>
<p>单价: ¥{product.price.toFixed(2)}</p>
</div>
<div className="item-controls">
<button
onClick={() => updateQuantity(product.id, product.quantity - 1)}
disabled={product.quantity <= 1}
>
-
</button>
<span>{product.quantity}</span>
<button onClick={() => updateQuantity(product.id, product.quantity + 1)}>
+
</button>
<button
onClick={() => removeProduct(product.id)}
className="btn-remove"
>
移除
</button>
</div>
<div className="item-total">
小计: ¥{(product.price * product.quantity).toFixed(2)}
</div>
</div>
))}
</div>
<div className="cart-summary">
<div className="discount-controls">
<button onClick={() => applyDiscount(10)}>9折优惠</button>
<button onClick={() => applyDiscount(20)}>8折优惠</button>
{hasDiscount && (
<button onClick={resetDiscount}>取消折扣</button>
)}
</div>
<div className="total-info">
<p>商品总数: {products.reduce((sum, p) => sum + p.quantity, 0)}</p>
{hasDiscount && <p>折扣金额: ¥{discount.toFixed(2)}</p>}
<h3>总计: ¥{calculateTotal().toFixed(2)}</h3>
</div>
</div>
</div>
);
}
useState用于在函数组件中添加状态管理