下面,我们来系统的梳理关于 State 的基本知识点:
一、State 核心概念
1.1 什么是 State?
State 是 React 组件内部管理动态数据的机制,具有以下关键特性:
- 组件私有:只能由组件自身修改
- 驱动渲染:state 变化触发组件重新渲染
- 局部性:每个组件实例拥有独立 state
- 异步更新:React 会批量处理 state 更新
1.2 State 与 Props 的区别
特性 | State | Props |
---|---|---|
所有权 | 组件内部 | 父组件传入 |
可变性 | 可修改 (setState ) | 只读 |
作用范围 | 组件内部 | 父子组件间通信 |
更新触发 | setState 调用 | 父组件重新渲染 |
二、State 基础使用
2.1 类组件 State
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }; // 初始化state
// 方法绑定
this.increment = this.increment.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>+</button>
</div>
);
}
}
2.2 函数组件 State (Hooks)
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 初始化state
const increment = () => {
setCount(count + 1); // 更新state
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
</div>
);
}
三、State 更新机制
3.1 异步批量更新
// 错误:连续调用不会累加
setCount(count + 1);
setCount(count + 1);
// 正确:使用函数式更新
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
3.2 合并更新(类组件)
// React 会自动合并对象更新
this.setState({ count: 1 });
this.setState({ flag: true });
// 等价于
this.setState({ count: 1, flag: true });
3.3 强制同步更新
// 在React事件处理中默认是批处理的
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// 这里只会触发一次渲染
}
// 强制同步更新(不推荐)
flushSync(() => {
setCount(c => c + 1);
});
// 这里会立即触发渲染
四、State 设计原则
4.1 最小化 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}`;
4.2 避免深层嵌套
// 反例:深层嵌套state
const [user, setUser] = useState({
profile: {
name: '',
address: {
city: '',
street: ''
}
}
});
// 正例:扁平化state
const [name, setName] = useState('');
const [city, setCity] = useState('');
const [street, setStreet] = useState('');
4.3 不可变更新
// 错误:直接修改state
const [todos, setTodos] = useState([...]);
todos[0].completed = true; // 错误!
// 正确:创建新对象/数组
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: true } : todo
)
);
五、高级 State 模式
5.1 状态提升
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<ChildA count={count} />
<ChildB onIncrement={() => setCount(c => c + 1)} />
</div>
);
}
5.2 状态机模式
function TrafficLight() {
const [state, setState] = useState('red');
const transitions = {
red: { next: 'green' },
green: { next: 'yellow' },
yellow: { next: 'red' }
};
const nextLight = () => {
setState(transitions[state].next);
};
return (
<div>
<Light color="red" active={state === 'red'} />
<Light color="yellow" active={state === 'yellow'} />
<Light color="green" active={state === 'green'} />
<button onClick={nextLight}>Next</button>
</div>
);
}
5.3 使用 useReducer
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
六、State 性能优化
6.1 避免不必要渲染
// 使用 React.memo 优化子组件
const Child = React.memo(function Child({ data }) {
// 只有当props变化时才会重新渲染
});
// 使用 useMemo 避免重复计算
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
6.2 惰性初始化 State
// 避免每次渲染都执行初始化函数
const [state, setState] = useState(() => {
const initialState = computeExpensiveValue();
return initialState;
});
6.3 状态分片
// 将大状态对象拆分为多个小状态
const [userInfo, setUserInfo] = useState({ ... });
// 拆分为:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// 这样能更精准控制更新范围
七、State 与副作用
7.1 数据获取
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(data => {
setUser(data);
setError(null);
})
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Profile user={user} />;
}
7.2 订阅/取消订阅
function OnlineStatus({ userId }) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
const subscription = subscribeToStatus(userId, status => {
setIsOnline(status);
});
return () => {
unsubscribeFromStatus(subscription);
};
}, [userId]);
// ...
}
八、常见问题与解决方案
8.1 Stale State 问题
// 问题:闭包中的旧state值
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setTimeout(() => {
// 这里获取的是点击时的count值
setCount(count + 1);
}, 1000);
};
// 解决:使用函数式更新
const incrementCorrect = () => {
setTimeout(() => {
setCount(prev => prev + 1);
}, 1000);
};
}
8.2 循环依赖
// 问题:state更新触发effect,effect又更新state
const [count, setCount] = useState(0);
useEffect(() => {
if (count < 10) {
setCount(count + 1); // 无限循环
}
}, [count]);
// 解决:检查是否需要更新
useEffect(() => {
if (count < 10 && someCondition) {
setCount(count + 1);
}
}, [count, someCondition]);
8.3 复杂状态管理
// 当组件状态过于复杂时:
// 方案1:拆分为多个小组件
// 方案2:使用 useReducer
// 方案3:考虑状态管理库(Redux, Zustand等)
九、实战案例
9.1 表单状态管理
function SignupForm() {
const [form, setForm] = useState({
username: '',
email: '',
password: '',
agreeTerms: false
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setForm(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// 提交逻辑...
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={form.username}
onChange={handleChange}
/>
{/* 其他表单项... */}
</form>
);
}
9.2 购物车状态
function Cart() {
const [items, setItems] = useState([]);
const [coupon, setCoupon] = useState('');
const addItem = (product) => {
setItems(prev => {
const existing = prev.find(item => item.id === product.id);
return existing
? prev.map(item =>
item.id === product.id
? { ...item, qty: item.qty + 1 }
: item
)
: [...prev, { ...product, qty: 1 }];
});
};
const total = items.reduce((sum, item) =>
sum + (item.price * item.qty), 0);
// ...
}
十、最佳实践总结
- 单一数据源:每个状态只保存在一个组件中
- 最小化原则:只存储必要数据,派生数据可即时计算
- 不可变性:总是通过创建新对象/数组来更新状态
- 合理拆分:复杂状态考虑拆分为多个useState或使用useReducer
- 性能意识:避免不必要的状态更新和组件重渲染
- 类型安全:使用TypeScript定义状态类型
- 测试友好:保持状态逻辑纯净,便于单元测试