State
下面,我们来系统的梳理关于 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); // 初始化stateconst 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}><inputname="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定义状态类型
- 测试友好:保持状态逻辑纯净,便于单元测试