State
State (状态) 是组件内部的数据,可以随时间变化
什么是 State?
State 是组件的内存。它用于存储组件在渲染过程中需要记住的信息。 当 State 改变时,React 会重新渲染组件。
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击了 {count} 次
</button>
);
}
State vs Props
Props 和 State 的主要区别:
- Props:像函数的参数,由父组件传递,只读
- State:像函数的局部变量,组件内部管理,可变
// Props - 外部传入
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// State - 内部管理
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
State 是私有的和封装的。 除非你显式地将它作为 Props 传递给子组件, 否则其他组件无法访问它。
使用 State
声明 State
使用 useState Hook 声明 State:
import { useState } from 'react';
function Example() {
// 语法:const [state, setState] = useState(initialValue)
const [count, setCount] = useState(0);
const [name, setName] = useState('Taylor');
const [isActive, setIsActive] = useState(false);
const [user, setUser] = useState({ name: 'Taylor', age: 25 });
// ...
}
TypeScript 类型注解
使用 TypeScript 时,你可以为 State 添加类型注解以获得更好的类型安全:
import { useState } from 'react';
// 基本类型
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isActive, setIsActive] = useState<boolean>(false);
// 对象类型 - 使用 interface
interface User {
name: string;
age: number;
email?: string;
}
const [user, setUser] = useState<User>({
name: 'Taylor',
age: 25
});
// 联合类型
type Status = 'idle' | 'loading' | 'success' | 'error';
const [status, setStatus] = useState<Status>('idle');
// 数组类型
const [items, setItems] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);
// 使用类型推断(推荐)
// TypeScript 会自动从初始值推断类型
const [count, setCount] = useState(0); // 推断为 number
读取 State
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
更新 State
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// 直接更新
setCount(count + 1);
// 或者使用函数更新
setCount(prev => prev + 1);
};
return (
<button onClick={increment}>
计数:{count}
</button>
);
}
永远不要直接修改 State。React 无法检测到直接修改, 必须使用设置函数来更新 State。
更新对象
当 State 是对象时,你需要创建一个新对象而不是修改现有对象:
const [user, setUser] = useState({
name: 'Taylor',
age: 25,
email: 'taylor@example.com'
});
// ❌ 错误:直接修改
user.age = 26;
// ✅ 正确:创建新对象
setUser({
...user,
age: 26
});
// ✅ 正确:使用函数更新
setUser(prevUser => ({
...prevUser,
age: 26
}));
更新嵌套对象
const [person, setPerson] = useState({
name: 'Taylor',
address: {
city: 'New York',
country: 'USA'
}
});
// 更新嵌套属性
setPerson({
...person,
address: {
...person.address,
city: 'Boston'
}
});
对于复杂的嵌套对象,可以使用 Immer 库来简化更新逻辑。 Immer 允许你编写看似直接修改的代码,但实际上会创建不可变更新。
更新数组
更新数组时,你需要创建一个新数组而不是修改现有数组:
添加元素
const [items, setItems] = useState([1, 2, 3]);
// 添加到末尾
setItems([...items, 4]);
// 添加到开头
setItems([0, ...items]);
删除元素
// 使用 filter 删除
setItems(items.filter(item => item !== 2));
// 使用 slice 删除
setItems([
...items.slice(0, index),
...items.slice(index + 1)
]);
更新元素
// 使用 map 更新
setItems(items.map((item, i) =>
i === index ? newValue : item
));
插入元素
// 在指定位置插入
setItems([
...items.slice(0, index),
newItem,
...items.slice(index)
]);
以下数组方法会修改原数组,不要使用:
push()、pop()splice()sort()、reverse()
以下数组方法会返回新数组,可以使用:
map()、filter()slice()、concat()spread([...arr])
函数式更新
当新 State 依赖于旧 State 时,使用函数式更新:
const [count, setCount] = useState(0);
// ❌ 可能有问题:基于旧 State 更新
const increment = () => {
setCount(count + 1);
setCount(count + 1);
// 两次都基于同一个 count 值,结果只增加 1
};
// ✅ 正确:使用函数更新
const increment = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 每次都基于最新的值,结果增加 2
};
当新 State 依赖于旧 State 时,总是使用函数式更新。 这可以避免由于 State 闭包导致的问题。
多个 State 变量
你可以使用多个 State 变量,或者将相关数据组合成一个对象:
多个独立变量
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
// 更新时互不影响
setName('Taylor');
setAge(25);
单个对象
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
// 更新时需要合并其他属性
setUser({ ...user, name: 'Taylor' });
如何选择?
使用多个 State 变量,当:
- 数据是相互独立的
- 数据不会一起更新
- 数据是不同的类型
使用单个对象,当:
- 数据是相关的
- 数据经常一起更新
- 数据是相同类型
初始 State
直接初始化
function Counter() {
const [count, setCount] = useState(0);
// ...
}
惰性初始化
如果初始状态需要通过复杂计算得出,使用函数可以避免每次渲染时重新计算:
function createInitialCount() {
console.log('这只运行一次');
return computeExpensiveValue();
}
function Counter() {
const [count, setCount] = useState(createInitialCount);
// ...
}
或者使用内联函数:
const [count, setCount] = useState(() => {
console.log('这只运行一次');
return computeExpensiveValue();
});
传递给 useState 的函数只在初始渲染时执行一次,
后续渲染会被跳过。这比直接在组件内部计算更高效。
State 更新批处理
React 会将多个 State 更新批处理为一次重新渲染,以提高性能:
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const handleClick = () => {
setCount(count + 1);
setName('Taylor');
// 这两个更新会被批处理为一次渲染
};
// ...
}
React 18 之前 vs 之后
在 React 18 中,自动批处理的范围更广,包括事件处理器、Promise、setTimeout 等:
// React 18 - 所有这些更新都会被批处理
function handleClick() {
setCount(c => c + 1);
setName('Taylor');
fetch('/api').then(() => {
setFlag(true);
setCount(c => c + 1);
// 这里也会被批处理
});
}
如果你需要在 State 更新后立即读取新值,可以使用 flushSync:
import { flushSync } from 'react-dom';
flushSync(() => {
setCount(count + 1);
});
// 现在可以立即读取新的 count 值
常见错误
直接修改 State
// ❌ 错误
items.push(newItem);
setItems(items);
// ✅ 正确
setItems([...items, newItem]);
在渲染中更新 State
// ❌ 错误:无限循环
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // 不要在渲染中更新
return <div>{count}</div>;
}
// ✅ 正确:在事件处理器中更新
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
使用旧 State
// ❌ 可能有问题
const increment = () => {
setCount(count + 1);
setCount(count + 1);
};
// ✅ 正确:使用函数更新
const increment = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
State 未初始化
// ❌ 错误:State 可能是 undefined
const [user, setUser] = useState();
return <div>{user.name}</div>;
// ✅ 正确:提供初始值
const [user, setUser] = useState({ name: '', age: 0 });
return <div>{user.name}</div>;
最佳实践
1. 保持 State 简单
只将渲染需要的数据放入 State。其他数据可以从 State 计算得出:
// ❌ 冗余的 State
const [items, setItems] = useState([1, 2, 3]);
const [totalCount, setTotalCount] = useState(items.length);
// ✅ 计算得出的值
const [items, setItems] = useState([1, 2, 3]);
const totalCount = items.length;
2. 避免重复的 State
// ❌ 重复的 State
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// ✅ 计算得出
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;
3. 避免深层嵌套的 State
如果 State 过于复杂,考虑使用 useReducer:
// 使用 useReducer 管理复杂状态
const [state, dispatch] = useReducer(reducer, {
user: { name: '', age: 0 },
posts: [],
comments: []
});
4. 使用 TypeScript 类型
interface User {
name: string;
age: number;
email: string;
}
const [user, setUser] = useState<User>({
name: '',
age: 0,
email: ''
});
何时使用 State?
使用 State 当:
- 数据随时间变化
- 数据不能从 Props 计算得出
- 数据是组件私有的
- 数据会影响渲染输出
不使用 State 当:
- 数据可以从 Props 计算得出
- 数据可以从其他 State 计算得出
- 数据不会改变
- 数据不影响渲染
问自己:“这个数据改变时,组件需要重新渲染吗?” 如果答案是“是”,使用 State。如果答案是“否”,使用普通变量或 ref。
React 19 新增:表单状态管理
React 19 引入了专门用于表单状态管理的新 Hooks,可以简化表单处理:
useActionState
useActionState 用于管理表单提交的状态(pending、error、success):
import { useActionState } from 'react';
function Form() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
// 提交表单
const result = await submitForm(formData);
return result;
},
null // 初始状态
);
return (
<form action={formAction}>
<input name="email" type="email" />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{state?.error && <div className="error">{state.error}</div>}
</form>
);
}
useOptimistic
useOptimistic 用于实现乐观更新(在服务器响应前先显示更新后的 UI):
import { useOptimistic } from 'react';
function LikeButton({ likes, onLike }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(state, newLike) => state + 1
);
return (
<button
onClick={() => {
addOptimisticLike({ type: 'like' });
onLike();
}}
>
❤️ {optimisticLikes}
</button>
);
}
这些新 Hooks 专门用于表单处理和乐观更新场景。
对于普通的状态管理,useState 仍然是首选。
相关 Hooks
- useState - 基本 State Hook
- useReducer - 复杂状态逻辑
- useContext - 跨组件共享状态
- useRef - 不触发渲染的引用
- useTransition - 非紧急状态更新
- useActionState - 表单状态管理(React 19+)
- useOptimistic - 乐观更新(React 19+)