Appearance
State概念与使用
学习目标
通过本章学习,你将掌握:
- State的概念和作用
- useState Hook的完整用法
- State与Props的区别
- 函数式更新与惰性初始化
- 多个State的管理策略
- State的最佳实践
- React 19中State的新特性
第一部分:State基础概念
1.1 什么是State
State(状态)是组件内部的私有数据,用于存储会随时间变化的信息。
State的本质
jsx
// State是组件的"记忆"
function Counter() {
// count是State,初始值为0
const [count, setCount] = useState(0);
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
// 每次点击按钮:
// 1. 调用setCount更新State
// 2. 触发组件重新渲染
// 3. count显示新值State的特点
jsx
// 1. 私有性:State属于组件内部
function ComponentA() {
const [value, setValue] = useState('A');
// value只在ComponentA中可用
return <div>{value}</div>;
}
function ComponentB() {
const [value, setValue] = useState('B');
// ComponentB有自己独立的State
return <div>{value}</div>;
}
// 2. 可变性:State可以更新
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? '开' : '关'}
</button>
);
}
// 3. 响应式:State更新触发重新渲染
function InputMirror() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<p>你输入了:{text}</p>
</>
);
}1.2 State vs Props
jsx
// Props:从父组件传递,不可修改
function Child({ name }) {
// name是props,只读
// name = 'New Name'; // 错误!
return <div>Hello, {name}</div>;
}
// State:组件内部数据,可以修改
function Parent() {
const [name, setName] = useState('Alice');
// 可以修改State
const changeName = () => {
setName('Bob');
};
return (
<>
<Child name={name} />
<button onClick={changeName}>改名</button>
</>
);
}
// 对比总结
/*
Props:
- 由父组件传递
- 只读,不可修改
- 用于组件间通信
- 类似函数参数
State:
- 组件内部定义
- 可以修改
- 触发重新渲染
- 类似局部变量
*/1.3 为什么需要State
jsx
// 问题:普通变量不能触发重新渲染
function BrokenCounter() {
let count = 0; // 普通变量
const increment = () => {
count++; // 修改变量
console.log(count); // 输出新值
// 但组件不会重新渲染,UI不更新
};
return (
<div>
<p>计数:{count}</p> {/* 永远显示0 */}
<button onClick={increment}>增加</button>
</div>
);
}
// 解决:使用State
function WorkingCounter() {
const [count, setCount] = useState(0); // State
const increment = () => {
setCount(count + 1); // 更新State
// 触发重新渲染,UI更新
};
return (
<div>
<p>计数:{count}</p> {/* 显示实时值 */}
<button onClick={increment}>增加</button>
</div>
);
}第二部分:useState Hook详解
2.1 基本用法
jsx
import { useState } from 'react';
function Example() {
// 语法:const [state, setState] = useState(initialValue)
const [count, setCount] = useState(0);
// 解构说明:
// count:当前State值
// setCount:更新State的函数
// 0:初始值
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}各种类型的State
jsx
function TypesExample() {
// 1. 数字State
const [count, setCount] = useState(0);
// 2. 字符串State
const [name, setName] = useState('Alice');
// 3. 布尔State
const [isVisible, setIsVisible] = useState(false);
// 4. 数组State
const [items, setItems] = useState([]);
// 5. 对象State
const [user, setUser] = useState({
name: 'Alice',
age: 25
});
// 6. null State
const [data, setData] = useState(null);
return (
<div>
<p>数字:{count}</p>
<p>字符串:{name}</p>
<p>布尔:{String(isVisible)}</p>
<p>数组长度:{items.length}</p>
<p>对象:{user.name}, {user.age}</p>
<p>Null:{data === null ? 'null' : data}</p>
</div>
);
}2.2 更新State
jsx
function UpdateExamples() {
const [count, setCount] = useState(0);
// 方式1:直接设置新值
const increment1 = () => {
setCount(count + 1);
};
// 方式2:函数式更新(推荐)
const increment2 = () => {
setCount(prevCount => prevCount + 1);
};
// 区别演示
const handleClick1 = () => {
setCount(count + 1); // count是0
setCount(count + 1); // count还是0
setCount(count + 1); // count还是0
// 最终count = 1(不是3)
};
const handleClick2 = () => {
setCount(prev => prev + 1); // prev是0,设为1
setCount(prev => prev + 1); // prev是1,设为2
setCount(prev => prev + 1); // prev是2,设为3
// 最终count = 3
};
return (
<div>
<p>计数:{count}</p>
<button onClick={handleClick1}>方式1: +3?</button>
<button onClick={handleClick2}>方式2: +3</button>
</div>
);
}对象State的更新
jsx
function ObjectStateExample() {
const [user, setUser] = useState({
name: 'Alice',
age: 25,
email: 'alice@example.com'
});
// 错误:直接修改State(不会触发更新)
const updateWrong = () => {
user.age = 26; // 错误!
setUser(user); // 不会重新渲染(引用相同)
};
// 正确:创建新对象
const updateRight = () => {
setUser({
...user, // 展开旧值
age: 26 // 覆盖age
});
};
// 更新单个属性
const updateName = (newName) => {
setUser(prev => ({
...prev,
name: newName
}));
};
// 更新多个属性
const updateMultiple = () => {
setUser(prev => ({
...prev,
age: 26,
email: 'new@example.com'
}));
};
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<p>邮箱:{user.email}</p>
<button onClick={updateRight}>更新年龄</button>
<button onClick={() => updateName('Bob')}>更新姓名</button>
<button onClick={updateMultiple}>更新多个</button>
</div>
);
}数组State的更新
jsx
function ArrayStateExample() {
const [items, setItems] = useState(['A', 'B', 'C']);
// 添加元素
const addItem = () => {
setItems([...items, 'New']); // 在末尾添加
// 或
setItems(prev => [...prev, 'New']);
};
const addToStart = () => {
setItems(['New', ...items]); // 在开头添加
};
// 删除元素
const removeFirst = () => {
setItems(items.slice(1)); // 删除第一个
};
const removeLast = () => {
setItems(items.slice(0, -1)); // 删除最后一个
};
const removeByIndex = (index) => {
setItems(items.filter((_, i) => i !== index));
};
// 更新元素
const updateByIndex = (index, newValue) => {
setItems(items.map((item, i) =>
i === index ? newValue : item
));
};
// 排序
const sortItems = () => {
setItems([...items].sort()); // 创建新数组再排序
};
return (
<div>
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => removeByIndex(index)}>删除</button>
<button onClick={() => updateByIndex(index, 'Updated')}>
更新
</button>
</li>
))}
</ul>
<button onClick={addItem}>添加到末尾</button>
<button onClick={addToStart}>添加到开头</button>
<button onClick={sortItems}>排序</button>
</div>
);
}2.3 惰性初始化
jsx
// 问题:昂贵的初始化计算每次渲染都执行
function SlowComponent() {
const [data, setData] = useState(
expensiveCalculation() // 每次渲染都调用
);
return <div>{data}</div>;
}
// 解决:使用函数形式(只在首次渲染时调用)
function FastComponent() {
const [data, setData] = useState(() => {
console.log('只在初始化时执行一次');
return expensiveCalculation();
});
return <div>{data}</div>;
}
// 实际应用
function UserComponent({ userId }) {
// 从localStorage读取(只在首次渲染时读取)
const [preferences, setPreferences] = useState(() => {
const saved = localStorage.getItem(`user-${userId}`);
return saved ? JSON.parse(saved) : DEFAULT_PREFERENCES;
});
// 复杂计算
const [computed, setComputed] = useState(() => {
return complexCalculation(userId);
});
return <div>{/* ... */}</div>;
}2.4 多个State的管理
jsx
function MultipleStates() {
// 方式1:分别定义(简单State)
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');
// 方式2:合并为对象(相关State)
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
// 方式3:使用useReducer(复杂逻辑)
const [state, dispatch] = useReducer(reducer, initialState);
return <div>{/* ... */}</div>;
}
// 何时分开,何时合并?
function WhenToMerge() {
// 分开:不相关的State
const [count, setCount] = useState(0);
const [theme, setTheme] = useState('light');
const [isOpen, setIsOpen] = useState(false);
// 合并:相关的State
const [form, setForm] = useState({
username: '',
password: '',
remember: false
});
// 合并:一起更新的State
const [position, setPosition] = useState({
x: 0,
y: 0
});
return <div>{/* ... */}</div>;
}第三部分:State的进阶用法
3.1 派生State(Derived State)
jsx
// 从props派生State(通常不推荐)
function SearchResults({ query }) {
const [localQuery, setLocalQuery] = useState(query);
// 问题:props变化时,localQuery不会更新
return <div>{localQuery}</div>;
}
// 解决方案1:直接使用props
function SearchResults({ query }) {
// 直接使用query,不需要State
const results = useMemo(() => {
return searchData(query);
}, [query]);
return <div>{results}</div>;
}
// 解决方案2:使用key重置组件
function Parent() {
const [query, setQuery] = useState('');
return (
<SearchResults
key={query} // query变化时重新创建组件
query={query}
/>
);
}
// 解决方案3:useEffect同步
function SearchResults({ query }) {
const [localQuery, setLocalQuery] = useState(query);
useEffect(() => {
setLocalQuery(query); // props变化时同步
}, [query]);
return <div>{localQuery}</div>;
}3.2 State与useEffect
jsx
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => {
if (isMounted) {
setData(data);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => {
isMounted = false; // 清理
};
}, [userId]); // userId变化时重新获取
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error.message}</div>;
if (!data) return null;
return <div>{data.name}</div>;
}3.3 状态提升(Lifting State Up)
jsx
// 问题:两个组件需要共享State
function TemperatureInput1() {
const [temperature, setTemperature] = useState('');
return (
<input
value={temperature}
onChange={e => setTemperature(e.target.value)}
/>
);
}
function TemperatureInput2() {
const [temperature, setTemperature] = useState('');
// 两个组件的State独立,无法同步
return (
<input
value={temperature}
onChange={e => setTemperature(e.target.value)}
/>
);
}
// 解决:提升State到共同父组件
function TemperatureConverter() {
const [temperature, setTemperature] = useState('');
return (
<div>
<h3>摄氏度</h3>
<TemperatureInput
value={temperature}
onChange={setTemperature}
/>
<h3>华氏度</h3>
<TemperatureInput
value={temperature ? (temperature * 9/5 + 32).toFixed(1) : ''}
onChange={value => setTemperature(
value ? ((value - 32) * 5/9).toFixed(1) : ''
)}
/>
</div>
);
}
function TemperatureInput({ value, onChange }) {
return (
<input
type="number"
value={value}
onChange={e => onChange(e.target.value)}
/>
);
}3.4 State批量更新
jsx
function BatchingExample() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
// React 18之前:同步更新会批量处理
const handleClickSync = () => {
setCount(c => c + 1); // 批量更新
setFlag(f => !f); // 批量更新
// 只触发一次重新渲染
};
// React 18之前:异步更新不会批量处理
const handleClickAsync = () => {
setTimeout(() => {
setCount(c => c + 1); // 单独更新
setFlag(f => !f); // 单独更新
// 触发两次重新渲染
}, 0);
};
// React 18+:自动批量处理所有更新
const handleClickAutomatic = () => {
setTimeout(() => {
setCount(c => c + 1); // 自动批量
setFlag(f => !f); // 自动批量
// 只触发一次重新渲染
}, 0);
fetch('/api').then(() => {
setCount(c => c + 1); // 自动批量
setFlag(f => !f); // 自动批量
// 只触发一次重新渲染
});
};
return (
<div>
<p>Count: {count}</p>
<p>Flag: {String(flag)}</p>
<button onClick={handleClickSync}>同步更新</button>
<button onClick={handleClickAsync}>异步更新</button>
<button onClick={handleClickAutomatic}>自动批量</button>
</div>
);
}第四部分:State设计原则
4.1 最小化State
jsx
// 不好:冗余State
function BadExample({ items }) {
const [items, setItems] = useState(props.items);
const [count, setCount] = useState(items.length); // 冗余
const [hasItems, setHasItems] = useState(items.length > 0); // 冗余
return <div>{count} items</div>;
}
// 好:计算派生值
function GoodExample({ items }) {
const count = items.length; // 计算
const hasItems = count > 0; // 计算
return <div>{count} items</div>;
}
// 不好:可以计算的State
function BadCalculation() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState(''); // 冗余
const handleFirstNameChange = (value) => {
setFirstName(value);
setFullName(`${value} ${lastName}`); // 需要手动同步
};
return <div>{fullName}</div>;
}
// 好:直接计算
function GoodCalculation() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`.trim(); // 自动同步
return <div>{fullName}</div>;
}4.2 避免State镜像Props
jsx
// 不好:State镜像Props
function BadComponent({ user }) {
const [currentUser, setCurrentUser] = useState(user);
// 问题:user变化时,currentUser不会更新
return <div>{currentUser.name}</div>;
}
// 好的方案
// 方案1:直接使用Props
function GoodComponent1({ user }) {
return <div>{user.name}</div>;
}
// 方案2:重命名Props表示初始值
function GoodComponent2({ initialUser }) {
const [user, setUser] = useState(initialUser);
// 只用于初始化,之后独立管理
return (
<div>
<p>{user.name}</p>
<button onClick={() => setUser({ ...user, name: 'New Name' })}>
改名
</button>
</div>
);
}
// 方案3:使用key重置
function Parent() {
const [selectedUser, setSelectedUser] = useState(users[0]);
return (
<UserEditor
key={selectedUser.id} // id变化时重新创建组件
user={selectedUser}
/>
);
}4.3 扁平化State结构
jsx
// 不好:深度嵌套
function BadNested() {
const [state, setState] = useState({
user: {
profile: {
name: 'Alice',
address: {
city: 'Beijing',
country: 'China'
}
},
settings: {
theme: 'light',
notifications: {
email: true,
push: false
}
}
}
});
// 更新很复杂
const updateCity = (city) => {
setState({
...state,
user: {
...state.user,
profile: {
...state.user.profile,
address: {
...state.user.profile.address,
city
}
}
}
});
};
return <div>{state.user.profile.address.city}</div>;
}
// 好:扁平化
function GoodFlat() {
const [name, setName] = useState('Alice');
const [city, setCity] = useState('Beijing');
const [country, setCountry] = useState('China');
const [theme, setTheme] = useState('light');
const [emailNotifications, setEmailNotifications] = useState(true);
const [pushNotifications, setPushNotifications] = useState(false);
// 更新简单
const updateCity = (newCity) => {
setCity(newCity);
};
return <div>{city}</div>;
}
// 或使用多个State对象(按功能分组)
function GoodGrouped() {
const [profile, setProfile] = useState({
name: 'Alice',
city: 'Beijing',
country: 'China'
});
const [settings, setSettings] = useState({
theme: 'light',
emailNotifications: true,
pushNotifications: false
});
const updateCity = (city) => {
setProfile({ ...profile, city });
};
return <div>{profile.city}</div>;
}4.4 规范化State
jsx
// 不好:重复的数据
function BadDuplication() {
const [posts, setPosts] = useState([
{
id: 1,
title: 'Post 1',
author: { id: 1, name: 'Alice' }
},
{
id: 2,
title: 'Post 2',
author: { id: 1, name: 'Alice' } // 重复
}
]);
// 更新作者名需要遍历所有帖子
const updateAuthorName = (authorId, newName) => {
setPosts(posts.map(post =>
post.author.id === authorId
? { ...post, author: { ...post.author, name: newName } }
: post
));
};
return <div>{/* ... */}</div>;
}
// 好:规范化(类似数据库)
function GoodNormalized() {
const [posts, setPosts] = useState({
1: { id: 1, title: 'Post 1', authorId: 1 },
2: { id: 2, title: 'Post 2', authorId: 1 }
});
const [authors, setAuthors] = useState({
1: { id: 1, name: 'Alice' }
});
// 更新作者名只需要一处修改
const updateAuthorName = (authorId, newName) => {
setAuthors({
...authors,
[authorId]: { ...authors[authorId], name: newName }
});
};
return <div>{/* ... */}</div>;
}第五部分:React 19的State新特性
5.1 自动批量更新
jsx
// React 19:所有更新都自动批量处理
function AutoBatching() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
// 事件处理器中
const handleClick = () => {
setCount(c => c + 1);
setFlag(f => !f);
// 自动批量,只触发一次渲染
};
// 异步回调中
const handleAsync = async () => {
const data = await fetchData();
setCount(data.count);
setFlag(data.flag);
// 自动批量,只触发一次渲染
};
// Promise中
const handlePromise = () => {
Promise.resolve().then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 自动批量,只触发一次渲染
});
};
// setTimeout中
const handleTimeout = () => {
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 自动批量,只触发一次渲染
}, 1000);
};
return (
<div>
<p>Count: {count}</p>
<p>Flag: {String(flag)}</p>
<button onClick={handleClick}>同步</button>
<button onClick={handleAsync}>异步</button>
<button onClick={handlePromise}>Promise</button>
<button onClick={handleTimeout}>Timeout</button>
</div>
);
}5.2 useTransition与State
jsx
import { useState, useTransition } from 'react';
function TransitionExample() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 高优先级更新:立即响应用户输入
setInput(e.target.value);
// 低优先级更新:可以延迟的列表更新
startTransition(() => {
const newList = generateLargeList(e.target.value);
setList(newList);
});
};
return (
<div>
<input value={input} onChange={handleChange} />
{isPending && <div>更新中...</div>}
<ul>
{list.map((item, i) => <li key={i}>{item}</li>)}
</ul>
</div>
);
}5.3 useDeferredValue与State
jsx
import { useState, useDeferredValue } from 'react';
function DeferredExample() {
const [input, setInput] = useState('');
// 延迟的值
const deferredInput = useDeferredValue(input);
// 使用延迟值进行昂贵的计算
const filteredList = useMemo(() => {
return largeList.filter(item =>
item.includes(deferredInput)
);
}, [deferredInput]);
return (
<div>
<input
value={input}
onChange={e => setInput(e.target.value)}
/>
<p>输入:{input}</p>
<p>延迟:{deferredInput}</p>
<List items={filteredList} />
</div>
);
}5.4 useOptimistic与State
jsx
import { useOptimistic, useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
);
async function addTodo(text) {
const newTodo = { id: Date.now(), text, pending: true };
// 乐观更新UI
addOptimisticTodo(newTodo);
try {
// 实际API调用
const savedTodo = await api.createTodo(text);
setTodos([...todos, savedTodo]);
} catch (error) {
// 失败时自动回滚
console.error('Failed to add todo');
}
}
return (
<div>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id}>
{todo.text}
{todo.pending && <span> (保存中...)</span>}
</li>
))}
</ul>
<button onClick={() => addTodo('New Todo')}>添加</button>
</div>
);
}第六部分:State性能优化
6.1 避免不必要的State更新
jsx
// 问题:相同值的更新也会触发渲染
function InefficientComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(0); // 即使值相同,也会触发渲染
};
return <button onClick={handleClick}>Count: {count}</button>;
}
// 解决:React会自动检测相同值(Object.is比较)
function EfficientComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(0); // React检测到值相同,不会重新渲染
};
return <button onClick={handleClick}>Count: {count}</button>;
}
// 对象State需要注意
function ObjectComponent() {
const [user, setUser] = useState({ name: 'Alice' });
const handleClick = () => {
setUser({ name: 'Alice' }); // 新对象,会触发渲染
// 即使内容相同
};
// 解决:检查是否真正改变
const handleClickSmart = () => {
setUser(prev =>
prev.name === 'Alice' ? prev : { name: 'Alice' }
);
};
return <button onClick={handleClickSmart}>Name: {user.name}</button>;
}6.2 使用useCallback缓存更新函数
jsx
import { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// 不好:每次渲染创建新函数
const increment = () => {
setCount(c => c + 1);
};
// 好:使用useCallback缓存
const incrementMemo = useCallback(() => {
setCount(c => c + 1);
}, []); // 依赖为空,函数永不变
return (
<div>
<p>Count: {count}</p>
<ChildComponent onIncrement={incrementMemo} />
</div>
);
}
const ChildComponent = React.memo(({ onIncrement }) => {
console.log('Child rendered');
return <button onClick={onIncrement}>Increment</button>;
});6.3 合理拆分State
jsx
// 不好:一个大State
function BadSplit() {
const [state, setState] = useState({
count: 0,
text: '',
isOpen: false,
theme: 'light'
});
// 任何一个值变化都会触发整个组件渲染
const updateCount = () => {
setState({ ...state, count: state.count + 1 });
};
return <div>{/* ... */}</div>;
}
// 好:拆分独立的State
function GoodSplit() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [theme, setTheme] = useState('light');
// 每个State独立更新
const updateCount = () => {
setCount(c => c + 1);
};
return <div>{/* ... */}</div>;
}
// 更好:按组件职责拆分
function BestSplit() {
return (
<>
<Counter /> {/* count独立 */}
<TextInput /> {/* text独立 */}
<Modal /> {/* isOpen独立 */}
<ThemeToggle /> {/* theme独立 */}
</>
);
}第七部分:常见问题与解决
7.1 State更新不及时
jsx
// 问题:使用旧的State值
function StaleState() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 输出旧值(0)
setTimeout(() => {
console.log(count); // 仍然是旧值(0)
}, 1000);
};
return <button onClick={handleClick}>{count}</button>;
}
// 解决:使用函数式更新
function FreshState() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => {
console.log('Previous:', prev); // 最新值
return prev + 1;
});
// 如果需要在更新后获取新值,使用useEffect
};
useEffect(() => {
console.log('Updated count:', count);
}, [count]);
return <button onClick={handleClick}>{count}</button>;
}7.2 State丢失或重置
jsx
// 问题:组件位置变化导致State重置
function BadPosition({ showA }) {
return (
<div>
{showA ? (
<Counter />
) : (
<div>
<Counter /> {/* 不同位置,会重新创建 */}
</div>
)}
</div>
);
}
// 解决:使用key保持State
function GoodPosition({ showA }) {
return (
<div>
{showA ? (
<Counter key="counter" />
) : (
<div>
<Counter key="counter" /> {/* 相同key,保持State */}
</div>
)}
</div>
);
}7.3 对象/数组State更新无效
jsx
// 问题:直接修改State
function BrokenUpdate() {
const [user, setUser] = useState({ name: 'Alice', age: 25 });
const updateAge = () => {
user.age = 26; // 直接修改
setUser(user); // 引用相同,不会更新
};
return <div>{user.age}</div>;
}
// 解决:创建新对象
function WorkingUpdate() {
const [user, setUser] = useState({ name: 'Alice', age: 25 });
const updateAge = () => {
setUser({ ...user, age: 26 }); // 新对象
// 或
setUser(prev => ({ ...prev, age: 26 }));
};
return <div>{user.age}</div>;
}第八部分:实战案例
8.1 表单State管理
jsx
function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
const [submitting, setSubmitting] = useState(false);
const handleChange = (field) => (e) => {
setFormData({
...formData,
[field]: e.target.value
});
// 清除该字段的错误
if (errors[field]) {
setErrors({
...errors,
[field]: undefined
});
}
};
const validate = () => {
const newErrors = {};
if (!formData.username) {
newErrors.username = '用户名不能为空';
}
if (!formData.email.includes('@')) {
newErrors.email = '邮箱格式错误';
}
if (formData.password.length < 6) {
newErrors.password = '密码至少6位';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = '两次密码不一致';
}
return newErrors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const newErrors = validate();
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
setSubmitting(true);
try {
await api.register(formData);
alert('注册成功!');
} catch (error) {
setErrors({ submit: error.message });
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
value={formData.username}
onChange={handleChange('username')}
placeholder="用户名"
/>
{errors.username && <span>{errors.username}</span>}
</div>
<div>
<input
type="email"
value={formData.email}
onChange={handleChange('email')}
placeholder="邮箱"
/>
{errors.email && <span>{errors.email}</span>}
</div>
<div>
<input
type="password"
value={formData.password}
onChange={handleChange('password')}
placeholder="密码"
/>
{errors.password && <span>{errors.password}</span>}
</div>
<div>
<input
type="password"
value={formData.confirmPassword}
onChange={handleChange('confirmPassword')}
placeholder="确认密码"
/>
{errors.confirmPassword && <span>{errors.confirmPassword}</span>}
</div>
{errors.submit && <div>{errors.submit}</div>}
<button type="submit" disabled={submitting}>
{submitting ? '提交中...' : '注册'}
</button>
</form>
);
}8.2 购物车State
jsx
function ShoppingCart() {
const [items, setItems] = useState([]);
const addItem = (product) => {
setItems(prev => {
const existing = prev.find(item => item.id === product.id);
if (existing) {
// 增加数量
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
} else {
// 添加新商品
return [...prev, { ...product, quantity: 1 }];
}
});
};
const removeItem = (productId) => {
setItems(prev => prev.filter(item => item.id !== productId));
};
const updateQuantity = (productId, quantity) => {
if (quantity <= 0) {
removeItem(productId);
return;
}
setItems(prev =>
prev.map(item =>
item.id === productId
? { ...item, quantity }
: item
)
);
};
const clearCart = () => {
setItems([]);
};
const total = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return (
<div>
<h2>购物车</h2>
{items.length === 0 ? (
<p>购物车是空的</p>
) : (
<>
<ul>
{items.map(item => (
<li 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))}
min="0"
/>
<span>小计:¥{item.price * item.quantity}</span>
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
<div>
<p>总计:¥{total.toFixed(2)}</p>
<button onClick={clearCart}>清空购物车</button>
<button>去结算</button>
</div>
</>
)}
</div>
);
}8.3 分页State管理
jsx
function PaginatedList({ pageSize = 10 }) {
const [data, setData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await api.getItems({
page: currentPage,
pageSize
});
setData(response.data);
setTotalPages(response.totalPages);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [currentPage, pageSize]);
const goToPage = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
const goToFirst = () => goToPage(1);
const goToLast = () => goToPage(totalPages);
const goToPrevious = () => goToPage(currentPage - 1);
const goToNext = () => goToPage(currentPage + 1);
if (loading) return <div>加载中...</div>;
return (
<div>
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div className="pagination">
<button onClick={goToFirst} disabled={currentPage === 1}>
首页
</button>
<button onClick={goToPrevious} disabled={currentPage === 1}>
上一页
</button>
<span>
第 {currentPage} / {totalPages} 页
</span>
<button onClick={goToNext} disabled={currentPage === totalPages}>
下一页
</button>
<button onClick={goToLast} disabled={currentPage === totalPages}>
末页
</button>
</div>
</div>
);
}练习题
基础练习
- 创建一个计数器,包含加、减、重置功能
- 实现一个切换开关组件
- 创建一个输入框,实时显示输入内容
进阶练习
- 实现一个待办事项列表(增删改)
- 创建一个表单验证系统
- 实现购物车功能(添加、删除、修改数量)
高级练习
- 实现一个支持撤销/重做的编辑器
- 创建一个复杂的分页数据管理系统
- 使用React 19的useOptimistic实现乐观更新
通过本章学习,你已经全面掌握了State的概念和使用方法。State是React的核心,理解State是掌握React的关键。继续学习,深入探索React的更多特性!