hook:useState
hook 的作用
使函数式组件也能有自身的状态和副作用,而无需编写类组件
-
注意
- 不能在类内使用,只能在函数内顶层调用 hook
- 不能在循环、条件或嵌套函数中调用 hook
- 但可以 自定义 hook(必须 use 开头) 来封装 底层 hook
useState
- 用来定义
状态变量,可以替代以前类里构造函数的 state 和 setState - 核心特点: 状态改变会触发组件重新渲染
场景代码示例
-
【场景 1】基础类型状态(最常用):
-
定义数字、字符串、布尔值等基础类型的状态
// 场景:计数器 - 使用数字状态 import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); // 初始值为 0 return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }; -
如果要用以前的类去写,会很麻烦
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; } render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1, }) } > Click me </button> </div> ); } }
-
-
【场景 2】对象状态(常用):
-
定义对象类型的状态,需要注意整体替换
// 场景:表单 - 使用对象状态 const Form = () => { const [user, setUser] = useState({ name: "", age: 0 }); // ❌ 错误:直接修改对象(React 不会检测到变化) const wrongUpdate = () => { user.name = "Alice"; // 不会触发重新渲染! }; // ✅ 正确:创建新对象 const correctUpdate = () => { setUser({ ...user, name: "Alice" }); // 展开运算符创建新对象 }; return ( <div> <p> Name: {user.name}, Age: {user.age} </p> <button onClick={correctUpdate}>Update Name</button> </div> ); }; -
注意:
setState是替换而非合并,必须用“展开运算符”保留其他属性
-
-
【场景 3】数组状态(常用):
-
定义数组类型的状态,需要使用不可变方法
// 场景:待办列表 - 使用数组状态 const TodoList = () => { const [todos, setTodos] = useState(["Learn React", "Build App"]); // ❌ 错误:直接修改数组 const wrongAdd = () => { todos.push("New Todo"); // 不会触发重新渲染! setTodos(todos); }; // ✅ 正确:创建新数组 const correctAdd = () => { setTodos([...todos, "New Todo"]); // 展开运算符创建新数组 }; const removeItem = (index) => { setTodos(todos.filter((_, i) => i !== index)); // filter 返回新数组 }; return ( <div> {todos.map((todo, i) => ( <div key={i}> {todo} <button onClick={() => removeItem(i)}>删除</button> </div> ))} <button onClick={correctAdd}>Add Todo</button> </div> ); }; -
常用不可变方法:
[...arr]、filter、map、slice、concat
-
-
【场景 4】函数式更新(重要):
-
基于前一个状态更新,避免闭包陷阱
// 场景:多次更新 - 使用函数式更新 const Counter = () => { const [count, setCount] = useState(0); // ❌ 错误:连续调用可能不生效 const wrongIncrement = () => { setCount(count + 1); // count 是闭包中的旧值 setCount(count + 1); // 还是基于旧值 setCount(count + 1); // 还是基于旧值 // 结果:只加 1,而不是加 3 }; // ✅ 正确:使用函数式更新 const correctIncrement = () => { setCount((prev) => prev + 1); // 基于最新值 setCount((prev) => prev + 1); // 基于最新值 setCount((prev) => prev + 1); // 基于最新值 // 结果:加 3 }; return ( <div> <p>Count: {count}</p> <button onClick={wrongIncrement}>错误 +3</button> <button onClick={correctIncrement}>正确 +3</button> </div> ); }; -
规则: 当新状态依赖旧状态时,使用函数式更新
setState(prev => newValue)
-
-
【场景 5】惰性初始化(优化):
-
初始值需要复杂计算时,使用函数避免每次渲染都计算
// 场景:昂贵的初始化 - 使用惰性初始化 const ExpensiveComponent = () => { // ❌ 每次渲染都会执行(即使用不到) const [data, setData] = useState(expensiveComputation()); // ✅ 只在初始化时执行一次 const [data2, setData2] = useState(() => expensiveComputation()); return <div>{data2}</div>; }; function expensiveComputation() { console.log("计算中..."); let result = 0; for (let i = 0; i < 1000000000; i++) { result += i; } return result; } -
适用场景: 从 localStorage 读取、复杂计算、解析大型数据
-
-
【场景 6】多个状态管理(常用):
-
定义多个独立的状态变量
// 场景:用户信息 - 多个独立状态 const UserProfile = () => { const [name, setName] = useState("Alice"); const [age, setAge] = useState(25); const [email, setEmail] = useState("alice@example.com"); // 或者合并为一个对象(看具体需求) const [user, setUser] = useState({ name: "Alice", age: 25, email: "alice@example.com", }); return ( <div> <p>Name: {name}</p> <p>Age: {age}</p> <p>Email: {email}</p> </div> ); }; -
选择原则:
- 相关联的数据 → 合并为一个对象
- 独立的数据 → 分开定义多个状态
-
状态更新方式对比
| 更新方式 | 代码示例 | 适用场景 |
|---|---|---|
| 直接赋值 | setCount(5) | 新值与旧值无关 |
| 基于当前值 | setCount(count + 1) | 单次更新,且值不在闭包中 |
| 函数式更新 | setCount(prev => prev + 1) | 基于旧值更新、连续更新 |
| 对象展开 | setUser({...user, age: 26}) | 更新对象的部分属性 |
| 数组展开 | setList([...list, item]) | 添加数组元素 |
| 数组过滤 | setList(list.filter(...)) | 删除数组元素 |
| 惰性初始化 | useState(() => compute()) | 初始值需要复杂计算 |
useState vs 类组件 state
| 特性 | useState | 类组件 state |
|---|---|---|
| 定义方式 | const [x, setX] = useState(0) | this.state = { x: 0 } |
| 更新方式 | setX(1) | this.setState({ x: 1 }) |
| 更新是否合并 | ❌ 替换 | ✅ 合并 |
| 可以定义多个 | ✅ 分开定义多个状态 | ❌ 只有一个 state 对象 |
| 函数式更新 | setX(prev => prev + 1) | this.setState(prev => ({...})) |
| 代码简洁度 | ✅ 更简洁 | ❌ 需要 this、constructor |
注意事项
-
⚠️ 不要直接修改状态
// ❌ 错误 const [user, setUser] = useState({ name: "Alice" }); user.name = "Bob"; // 不会触发重新渲染 // ✅ 正确 setUser({ ...user, name: "Bob" }); // 创建新对象 -
⚠️ 状态更新是异步的
setCount(count + 1); console.log(count); // 还是旧值,不会立即更新 -
⚠️ 对象/数组需要创建新引用
// ❌ React 不会检测到变化 const [list, setList] = useState([1, 2, 3]); list.push(4); setList(list); // 引用没变,不会重新渲染 // ✅ 创建新数组 setList([...list, 4]); // 新引用,触发重新渲染 -
✅ 连续更新使用函数式更新
// 多次更新时,使用函数形式保证基于最新值 setCount((prev) => prev + 1); setCount((prev) => prev + 1); -
✅ 初始化开销大时使用惰性初始化
// 使用函数,只在初始化时执行一次 useState(() => expensiveComputation());