useState:组件的"记忆"
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// ↑ 当前值 ↑ 改它的函数 ↑ 初值
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
每次点击 → setCount 更新 → React 重新渲染组件 → 显示新值。
多个 state
function UserForm() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
return (
<form>
<input value={name} onChange={e => setName(e.target.value)} />
<input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
<p>{name}, {age}</p>
</form>
);
}
函数式更新(基于上次值)
// ❌ 容易错
setCount(count + 1);
// ✓ 当依赖前次值时更稳
setCount(prev => prev + 1);
何时必须用函数式:在 setInterval / setTimeout / 异步代码里。
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1); // ✓ 不会"卡在" 0
}, 1000);
return () => clearInterval(id);
}, []);
对象 / 数组 state
永远返回新对象 / 数组——不要 mutate:
// ❌ 改了 React 不知道
user.name = 'Bob';
setUser(user);
// ✓ 返回新对象
setUser({ ...user, name: 'Bob' });
// 数组也是
setItems([...items, newItem]); // 加
setItems(items.filter(i => i.id !== id)); // 删
setItems(items.map(i => i.id === id ? { ...i, done: true } : i)); // 改
事件处理
onClick
<button onClick={() => alert('hi')}>Click</button>
// 提取函数
function handleClick() {
alert('hi');
}
<button onClick={handleClick}>Click</button>
// 拿事件对象
<button onClick={(e) => {
console.log(e.currentTarget);
e.preventDefault();
}}>Click</button>
⚠ 区别:
onClick={handleClick} // ✓ 引用函数(React 在点击时调用)
onClick={handleClick()} // ❌ 立即调用(页面一渲染就触发)
onChange(输入)
const [text, setText] = useState('');
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
这是受控组件——React 控制 input 的值。
onSubmit(表单)
function handleSubmit(e: React.FormEvent) {
e.preventDefault(); // ★ 阻止页面刷新
console.log('submitting');
}
<form onSubmit={handleSubmit}>
<input name="email" />
<button type="submit">Submit</button>
</form>
传参数
function handleDelete(id: number) {
setItems(items.filter(i => i.id !== id));
}
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => handleDelete(item.id)}>×</button>
</li>
))}
state 是异步的
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // 还是旧值!
// count 在下次渲染时才更新
}
如果需要立即拿到新值:
function handleClick() {
const newCount = count + 1;
setCount(newCount);
console.log(newCount); // ✓ 拿到新值
}
完整例子:todo list
'use client';
import { useState } from 'react';
interface Todo {
id: number;
text: string;
done: boolean;
}
export default function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState('');
function add() {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput('');
}
function toggle(id: number) {
setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
}
function remove(id: number) {
setTodos(todos.filter(t => t.id !== id));
}
return (
<div className="p-4">
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={add}>Add</button>
<ul>
{todos.map(t => (
<li key={t.id}>
<input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} />
<span style={{ textDecoration: t.done ? 'line-through' : 'none' }}>
{t.text}
</span>
<button onClick={() => remove(t.id)}>×</button>
</li>
))}
</ul>
</div>
);
}
注意第一行 'use client'; —— Next.js 里使用 useState 需要标记客户端组件(Server Component 不能用 useState)。详见 09-pages-routes。
坑
- 永远返回新对象 / 数组,不要 mutate
- onClick 别加
()—— 加了立即调用 - state 异步——
console.log(count)后面拿到的是旧值 - Next.js 用 useState 要
'use client'标记
下一篇:Tailwind 样式集成。