State(状态)是组件内部管理的可变数据。与 props 不同,state 是私有的,完全受控于组件本身。
State 的特点:
什么时候用 State:
什么时候不用 State:
初始化 State:
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0, name: '计数器' }; } // 或者使用类属性语法 state = { count: 0, name: '计数器' }; }
读取 State:
class Counter extends React.Component { state = { count: 0 }; render() { return <div>{this.state.count}</div>; } }
更新 State:
class Counter extends React.Component { state = { count: 0 }; increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>{this.state.count}</p> <button onClick={this.increment}>+1</button> </div> ); } }
State 更新的合并:
setState 会将提供的对象浅合并到当前 state。
// state = { count: 0, name: 'test' } this.setState({ count: 1 }); // state 变为 { count: 1, name: 'test' } // name 没有被改变
基于前一个 State 更新:
由于 setState 可能是异步的,依赖前一个 state 时应该使用函数形式。
// 可能不正确 this.setState({ count: this.state.count + 1 }); // 正确 this.setState((prevState) => ({ count: prevState.count + 1 })); // 使用第二个参数 props this.setState((prevState, props) => ({ count: prevState.count + props.increment }));
State 更新后的回调:
setState 的第二个参数是回调函数,在 state 更新并重新渲染后执行。
this.setState( { count: this.state.count + 1 }, () => { console.log('Count updated:', this.state.count); } );
useState Hook 让函数组件也能使用 state。
基本用法:
import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }
useState 返回数组:
初始值:
useState 的参数是初始值,只在首次渲染时使用。
// 简单初始值 const [count, setCount] = useState(0); // 函数式初始值(适用于复杂计算) const [state, setState] = useState(() => { const initialState = someExpensiveComputation(); return initialState; });
更新 State:
// 直接设置新值 setCount(5); // 基于前一个 state 更新 setCount(prevCount => prevCount + 1); // 更新对象 const [user, setUser] = useState({ name: '张三', age: 25 }); // 错误:直接修改 user.age = 26; setUser(user); // 不会触发重新渲染 // 正确:创建新对象 setUser({ ...user, age: 26 }); // 使用函数式更新 setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));
多个 State:
function Form() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [age, setAge] = useState(0); // 或者使用一个对象 const [form, setForm] = useState({ name: '', email: '', age: 0 }); }
React 中的 state 应该被视为不可变的。
为什么不可变性很重要:
处理数组:
const [items, setItems] = useState(['a', 'b', 'c']); // 添加元素 setItems([...items, 'd']); // 插入元素 setItems([...items.slice(0, 1), 'x', ...items.slice(1)]); // 删除元素 setItems(items.filter((_, index) => index !== 1)); // 修改元素 setItems(items.map((item, index) => index === 1 ? 'modified' : item )); // 排序(先复制) setItems([...items].sort());
处理对象:
const [user, setUser] = useState({ name: '张三', address: { city: '北京', street: '长安街' } }); // 更新顶层属性 setUser({ ...user, name: '李四' }); // 更新嵌套对象(注意深拷贝问题) setUser({ ...user, address: { ...user.address, city: '上海' } }); // 或者使用 immer import produce from 'immer'; setUser(produce(user, draft => { draft.address.city = '上海'; }));
| 特性 | Props | State |
| —— | ——- | ——- |
| 来源 | 父组件传递 | 组件内部管理 |
| 可读性 | 只读 | 可读写 |
| 修改 | 不能修改 | 通过 setState 修改 |
| 作用域 | 父子组件通信 | 组件内部 |
| 触发渲染 | props 变化触发重新渲染 | state 变化触发重新渲染 |
数据流:
当多个组件需要共享状态时,将状态提升到它们的最近公共祖先组件。
// 温度转换器示例 function BoilingVerdict({ celsius }) { if (celsius >= 100) { return <p>水会沸腾</p>; } return <p>水不会沸腾</p>; } class Calculator extends React.Component { state = { temperature: '' }; handleChange = (e) => { this.setState({ temperature: e.target.value }); }; render() { const { temperature } = this.state; return ( <fieldset> <legend>输入温度:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
状态提升的步骤:
1. 找到需要共享 state 的组件 2. 找到它们的最近公共祖先 3. 将 state 定义在祖先组件中 4. 通过 props 将 state 传递给子组件 5. 通过回调函数让子组件修改 state
某些情况下,state 可以从 props 派生出来。
反模式:复制 props 到 state:
// 不要这样做 class EmailInput extends React.Component { state = { email: this.props.email // 复制 props 到 state }; // 问题:props 更新时 state 不会更新 }
正确使用派生 state:
// 方法 1:完全受控组件 function EmailInput({ email, onChange }) { return <input value={email} onChange={onChange} />; } // 方法 2:使用 key 强制重新挂载 <EmailInput key={userId} defaultEmail={user.email} /> // 方法 3:只在 prop 真正变化时更新 state class EmailInput extends React.Component { state = { email: this.props.defaultEmail }; componentDidUpdate(prevProps) { if (prevProps.defaultEmail !== this.props.defaultEmail) { this.setState({ email: this.props.defaultEmail }); } } }
React 18 引入了自动批处理,多个 state 更新会被合并为一次重新渲染。
function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { setCount(c => c + 1); // 不会立即重新渲染 setFlag(f => !f); // 不会立即重新渲染 // React 会等到所有 state 更新完成后再重新渲染 } console.log('Render'); // 只会打印一次 return ( <div> <button onClick={handleClick}>点击</button> </div> ); }
flushSync:
如果需要强制同步更新,可以使用 flushSync(不推荐常规使用):
import { flushSync } from 'react-dom'; function handleClick() { flushSync(() => { setCount(c => c + 1); }); // 到这里 DOM 已经更新了 flushSync(() => { setFlag(f => !f); }); }
本章深入讲解了 Props 和 State:
理解 state 是 React 开发的核心,下一章我们将学习事件处理。