React State 基础概念
State 是 React 组件中用于存储和管理数据的核心机制。与 props 不同,state 是组件内部私有的,允许组件在用户交互或数据变化时动态更新 UI。State 的设计遵循单向数据流原则,确保可预测性。
Class 组件通过 this.state 和 this.setState() 管理状态,而函数组件使用 useState Hook。State 的更新可能是异步的,React 会批量处理以提高性能。直接修改 this.state 不会触发重新渲染,必须通过 setState 或 Hook 的更新函数。
类组件中的 State
在类组件中,state 通常在构造函数中初始化:
javascript
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
}
更新 state 需使用 setState 方法,可接受对象或函数:
javascript
// 对象形式
this.setState({ count: this.state.count + 1 });
// 函数形式(依赖前一个状态)
this.setState((prevState) => ({ count: prevState.count + 1 }));
State 更新会触发组件的重新渲染。对于复杂状态,建议将 state 拆分为多个独立变量,避免深层嵌套。
函数组件中的 State
函数组件通过 useState Hook 管理状态:
javascript
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
}
useState 返回当前状态和更新函数。更新函数会替换整个状态,而非合并:
javascript
setCount(count + 1); // 直接替换
对于对象类型的状态,需手动合并属性:
javascript
const [user, setUser] = useState({ name: 'Alice', age: 25 });
setUser({ ...user, age: 26 }); // 展开运算符合并
State 的设计原则
单一职责原则 :每个 state 变量应只负责一个数据点。避免将不相关的数据合并到一个 state 对象中。例如,将 username 和 isLoading 拆分为两个独立 state。
最小化状态 :只将需要触发 UI 更新的数据放入 state。派生数据可通过 props 或计算获得。例如,从 items 数组计算 totalCount 而非单独存储。
提升状态:当多个组件需要共享状态时,将 state 提升至最近的共同祖先。通过 props 向下传递数据,通过函数向上传递更新逻辑。
State 的性能优化
避免不必要的渲染 :使用 React.memo 或 shouldComponentUpdate 防止子组件因父组件 state 变化而重复渲染。对于函数组件,useMemo 和 useCallback 可缓存计算结果和函数引用。
批量更新 :React 会自动批量处理同步代码中的 state 更新。在异步操作(如 setTimeout 或 Promise)中,需手动使用 unstable_batchedUpdates 或依赖 Hook 的自动批处理特性。
惰性初始化 :对于耗时的初始状态计算,可传递函数给 useState:
javascript
const [data, setData] = useState(() => computeExpensiveValue());
复杂状态管理
对于跨组件或复杂状态逻辑,考虑使用 Context API 或状态管理库(如 Redux)。Context 适用于全局数据(如主题、用户信息),而 Redux 适合高频更新的应用级状态。
使用 useReducer Hook 处理包含多个子值的复杂 state:
javascript
const [state, dispatch] = useReducer(reducer, initialState);
Reducer 函数接收当前 state 和 action,返回新 state:
javascript
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
default:
throw new Error();
}
}
State 与副作用
副作用(如数据获取)应通过 useEffect Hook 处理。依赖数组指定 state 变化时触发的副作用:
javascript
useEffect(() => {
fetchData().then(setData);
}, [query]); // 仅在 query 变化时执行
避免在渲染过程中直接执行副作用。对于竞态条件,可使用清理函数:
javascript
useEffect(() => {
let ignore = false;
fetchData().then(data => !ignore && setData(data));
return () => { ignore = true; };
}, [id]);
常见陷阱与解决方案
过时闭包:在异步回调中访问 state 可能获取旧值。函数式更新或使用 ref 保存最新值可解决:
javascript
setCount(prev => prev + 1); // 函数式更新
深层比较 :React 使用 Object.is 比较 state。更新对象或数组时需创建新引用:
javascript
setItems([...items, newItem]); // 数组更新
setObj({ ...obj, key: value }); // 对象更新
循环依赖:避免在 effect 中设置依赖该 effect 的 state,否则会导致无限循环。