状态管理基础
什么是状态
状态(State)是组件的数据,当状态改变时,组件会重新渲染。
import { useState } from 'react';
function Counter() {
// count 是状态,setCount 是更新状态的函数
const [count, setCount] = useState(0);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useState 详解
基本类型状态
function Example() {
const [text, setText] = useState(''); // 字符串
const [count, setCount] = useState(0); // 数字
const [isOpen, setIsOpen] = useState(false); // 布尔值
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<p>计数: {count}</p>
<p>状态: {isOpen ? '开' : '关'}</p>
</div>
);
}
对象状态
function UserForm() {
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
// 更新单个字段
const updateName = (name) => {
setUser({ ...user, name });
};
// 通用更新函数
const updateField = (field, value) => {
setUser({ ...user, [field]: value });
};
return (
<div>
<input
value={user.name}
onChange={e => updateField('name', e.target.value)}
placeholder="姓名"
/>
<input
type="number"
value={user.age}
onChange={e => updateField('age', Number(e.target.value))}
placeholder="年龄"
/>
<input
value={user.email}
onChange={e => updateField('email', e.target.value)}
placeholder="邮箱"
/>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
);
}
数组状态
function ShoppingList() {
const [items, setItems] = useState([]);
const [input, setInput] = useState('');
// 添加项目
const addItem = () => {
setItems([...items, { id: Date.now(), name: input }]);
setInput('');
};
// 删除项目
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
// 更新项目
const updateItem = (id, newName) => {
setItems(items.map(item =>
item.id === id ? { ...item, name: newName } : item
));
};
// 清空列表
const clearAll = () => {
setItems([]);
};
return (
<div>
<input
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={addItem}>添加</button>
<button onClick={clearAll}>清空</button>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
函数式更新
function Counter() {
const [count, setCount] = useState(0);
// ❌ 可能出问题
const increment = () => {
setCount(count + 1);
setCount(count + 1); // 还是只加 1
};
// ✅ 正确方式
const incrementTwice = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 会加 2
};
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>错误的 +2</button>
<button onClick={incrementTwice}>正确的 +2</button>
</div>
);
}
惰性初始化
function ExpensiveComponent() {
// ❌ 每次渲染都会执行
const [data, setData] = useState(expensiveCalculation());
// ✅ 只在初始化时执行一次
const [data2, setData2] = useState(() => expensiveCalculation());
return <div>{data}</div>;
}
状态更新原则
1. 不要直接修改状态
function TodoList() {
const [todos, setTodos] = useState([]);
// ❌ 错误:直接修改
const addTodo = (text) => {
todos.push({ id: Date.now(), text });
setTodos(todos); // 不会触发更新
};
// ✅ 正确:创建新数组
const addTodoCorrect = (text) => {
setTodos([...todos, { id: Date.now(), text }]);
};
return <div>...</div>;
}
2. 状态更新是异步的
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 还是旧值
// 如果需要使用新值
setCount(prev => {
console.log(prev + 1); // 新值
return prev + 1;
});
};
return <button onClick={handleClick}>+1</button>;
}
3. 批量更新
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const handleSubmit = () => {
// React 会批量处理这些更新,只渲染一次
setName('张三');
setAge(25);
};
return <button onClick={handleSubmit}>提交</button>;
}
状态设计原则
1. 最小化状态
// ❌ 不好:冗余状态
function UserProfile() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState(''); // 冗余
return <div>{fullName}</div>;
}
// ✅ 好:计算派生值
function UserProfile() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`; // 直接计算
return <div>{fullName}</div>;
}
2. 避免重复状态
// ❌ 不好:props 复制到 state
function Message({ initialText }) {
const [text, setText] = useState(initialText);
// initialText 变化时,text 不会更新
return <div>{text}</div>;
}
// ✅ 好:直接使用 props
function Message({ text }) {
return <div>{text}</div>;
}
// ✅ 或者使用 key 重置
function Parent() {
const [text, setText] = useState('hello');
return <Message key={text} initialText={text} />;
}
3. 扁平化状态
// ❌ 不好:深层嵌套
const [state, setState] = useState({
user: {
profile: {
name: '',
address: {
city: '',
street: ''
}
}
}
});
// ✅ 好:扁平化
const [userName, setUserName] = useState('');
const [city, setCity] = useState('');
const [street, setStreet] = useState('');
多个相关状态
使用 useReducer
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'SET_NAME':
return { ...state, name: action.payload };
case 'SET_AGE':
return { ...state, age: action.payload };
case 'RESET':
return { name: '', age: 0 };
default:
return state;
}
}
function Form() {
const [state, dispatch] = useReducer(reducer, {
name: '',
age: 0
});
return (
<div>
<input
value={state.name}
onChange={e => dispatch({ type: 'SET_NAME', payload: e.target.value })}
/>
<input
type="number"
value={state.age}
onChange={e => dispatch({ type: 'SET_AGE', payload: Number(e.target.value) })}
/>
<button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
</div>
);
}
实战示例
购物车
import { useState } from 'react';
function ShoppingCart() {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
const existing = cart.find(item => item.id === product.id);
if (existing) {
// 增加数量
setCart(cart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
// 添加新商品
setCart([...cart, { ...product, quantity: 1 }]);
}
};
const removeFromCart = (id) => {
setCart(cart.filter(item => item.id !== id));
};
const updateQuantity = (id, quantity) => {
if (quantity <= 0) {
removeFromCart(id);
} else {
setCart(cart.map(item =>
item.id === id ? { ...item, quantity } : item
));
}
};
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
<div>
<h2>购物车</h2>
{cart.map(item => (
<div key={item.id}>
<span>{item.name}</span>
<span>¥{item.price}</span>
<input
type="number"
value={item.quantity}
onChange={e => updateQuantity(item.id, Number(e.target.value))}
/>
<button onClick={() => removeFromCart(item.id)}>删除</button>
</div>
))}
<h3>总计: ¥{total}</h3>
</div>
);
}