React Hooks 与状态管理
前端框架 Hooks useState一、声明式 vs 命令式编程
1.1 两种范式对比
| 范式 | 说明 | 类比 |
|---|---|---|
| 命令式 | 告诉计算机 怎么做(每一步) | 手动挡汽车 |
| 声明式 | 告诉计算机 要什么(描述结果) | 自动挡汽车 |
1.2 代码对比
javascript
// 命令式 — 手动操作 DOM
const h1 = document.createElement("h1");
h1.textContent = "Hello";
h1.style.color = isNight ? "white" : "black";
document.getElementById("root").appendChild(h1);
// 声明式(React)— 描述 UI 应该是什么样
function App() {
return <h1 style={{ color: isNight ? "white" : "black" }}>Hello</h1>;
}💡 React 是 声明式 框架:你描述 UI 应该呈现的状态,React 负责高效更新 DOM。
二、useState Hook
2.1 为什么需要 State
普通变量改变 不会 触发 React 重新渲染:
javascript
// ❌ 变量改变,但 UI 不更新
let count = 0;
function App() {
function increase() {
count++; // 值确实变了
console.log(count); // 控制台看到增加
// 但 UI 不会更新!React 不知道要重新渲染
}
return (
<div>
<h1>{count}</h1>
<button onClick={increase}>+</button>
</div>
);
}2.2 useState 基础
javascript
import React, { useState } from "react";
function App() {
// 数组解构:[当前值, 更新函数] = useState(初始值)
const [count, setCount] = useState(0);
// ↑ 当前状态值
// ↑ 更新状态的函数
function increase() {
setCount(count + 1); // ✅ 调用 setter → 触发重新渲染
}
return (
<div>
<h1>{count}</h1>
<button onClick={increase}>+</button>
</div>
);
}2.3 工作原理
初始渲染:
useState(0) → count = 0, setCount = 更新函数
UI 显示: [0] [+]
用户点击 [+]:
setCount(0 + 1) → React 计划重新渲染
重新渲染:
useState(0) → count = 1(React 记住了新值)
UI 显示: [1] [+]
再次点击 [+]:
setCount(1 + 1) → count = 2
UI 显示: [2] [+]2.4 useState 的命名约定
javascript
const [value, setValue] = useState(initialValue);
// 名词 set+名词
// 示例
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [isVisible, setIsVisible] = useState(false);
const [items, setItems] = useState([]);
const [formData, setFormData] = useState({ name: "", email: "" });三、事件处理
3.1 基本事件绑定
jsx
function App() {
function handleClick() {
console.log("Button clicked!");
}
return <button onClick={handleClick}>Click Me</button>;
// ↑ 传函数引用,不是调用结果
}常见错误
jsx
// ❌ 加了 () → 渲染时立即执行,而非点击时执行
<button onClick={handleClick()}>Click Me</button>
// ✅ 只传引用 → 点击时才执行
<button onClick={handleClick}>Click Me</button>
// ✅ 需要传参时,用箭头函数包裹
<button onClick={() => handleClick(id)}>Delete</button>3.2 常用事件
| 事件 | 触发时机 | 示例 |
|---|---|---|
onClick | 点击 | <button onClick={fn}> |
onChange | 输入变化 | <input onChange={fn}> |
onSubmit | 表单提交 | <form onSubmit={fn}> |
onMouseOver | 鼠标悬停 | <div onMouseOver={fn}> |
onMouseOut | 鼠标离开 | <div onMouseOut={fn}> |
onKeyDown | 按键按下 | <input onKeyDown={fn}> |
3.3 事件对象
javascript
function handleChange(event) {
console.log(event.target.name); // 输入框的 name 属性
console.log(event.target.value); // 当前输入的值
}
<input name="username" onChange={handleChange} />四、受控组件与表单
4.1 受控组件
React 管理输入框的值 → 输入框的 value 由 state 驱动:
javascript
function App() {
const [name, setName] = useState("");
function handleChange(event) {
setName(event.target.value);
}
return (
<input
type="text"
value={name} // 受控:值来自 state
onChange={handleChange} // 每次输入都更新 state
placeholder="Your name"
/>
);
}用户输入 "A":
onChange 触发 → setName("A") → state 更新 → React 重新渲染
→ input 的 value 变为 "A"
用户继续输入 "n":
onChange 触发 → setName("An") → state 更新 → 重新渲染
→ input 的 value 变为 "An"4.2 表单提交
javascript
function App() {
const [name, setName] = useState("");
const [greeting, setGreeting] = useState("");
function handleChange(event) {
setName(event.target.value);
}
function handleSubmit(event) {
event.preventDefault(); // ← 阻止页面刷新!
setGreeting("Hello " + name);
setName(""); // 清空输入框
}
return (
<div>
<h1>{greeting}</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={handleChange}
placeholder="Enter your name"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}event.preventDefault()
不调用 event.preventDefault() → 表单提交会导致 页面刷新 → React 状态全部丢失!
五、复杂状态管理
5.1 对象状态
管理多个相关字段时,使用 对象 作为 state:
javascript
function App() {
const [contact, setContact] = useState({
fName: "",
lName: "",
email: ""
});
function handleChange(event) {
const { name, value } = event.target; // 解构
setContact(prevValue => ({
...prevValue, // 展开保留其他字段
[name]: value // 计算属性名 → 只更新当前字段
}));
}
return (
<form>
<input name="fName" value={contact.fName} onChange={handleChange} />
<input name="lName" value={contact.lName} onChange={handleChange} />
<input name="email" value={contact.email} onChange={handleChange} />
</form>
);
}5.2 为什么用展开运算符
javascript
// ❌ 只设置一个字段 → 其他字段丢失!
setContact({ fName: value });
// contact 变成 { fName: "Angela" } — lName 和 email 消失了!
// ✅ 展开保留 + 覆盖目标字段
setContact(prev => ({
...prev, // { fName: "...", lName: "...", email: "..." }
[name]: value // 只覆盖 name 对应的那个字段
}));5.3 计算属性名
javascript
const key = "fName";
const obj = { [key]: "Angela" };
// → { fName: "Angela" }
// 在 handleChange 中:
const { name, value } = event.target;
// name = "fName", value = "Angela"
// [name]: value → fName: "Angela"5.4 数组状态
javascript
function App() {
const [items, setItems] = useState([]);
function addItem(newItem) {
// ✅ 函数式更新 + 展开
setItems(prevItems => [...prevItems, newItem]);
}
function deleteItem(id) {
setItems(prevItems => prevItems.filter((item, index) => index !== id));
}
return (
<div>
{items.map((item, index) => (
<div key={index}>
<span>{item}</span>
<button onClick={() => deleteItem(index)}>×</button>
</div>
))}
</div>
);
}函数式更新
当新状态 依赖 旧状态时,始终使用 函数式更新:
javascript
// ❌ 可能使用过时的 state 值
setCount(count + 1);
// ✅ 始终基于最新的 prevState
setCount(prev => prev + 1);
setItems(prev => [...prev, newItem]);
setContact(prev => ({ ...prev, [name]: value }));六、组件间通信
6.1 父 → 子:Props
jsx
// 父组件传递数据
<Note title="Buy milk" content="2 bottles" />
// 子组件接收
function Note({ title, content }) {
return <div><h1>{title}</h1><p>{content}</p></div>;
}6.2 子 → 父:回调函数
通过 props 传递 函数 给子组件,子组件调用该函数 向上传递数据:
jsx
// 父组件
function App() {
const [notes, setNotes] = useState([]);
function addNote(newNote) {
setNotes(prev => [...prev, newNote]);
}
function deleteNote(id) {
setNotes(prev => prev.filter((_, index) => index !== id));
}
return (
<div>
<CreateArea onAdd={addNote} />
{notes.map((note, index) => (
<Note
key={index}
id={index}
title={note.title}
content={note.content}
onDelete={deleteNote}
/>
))}
</div>
);
}jsx
// 子组件 — CreateArea
function CreateArea({ onAdd }) {
const [note, setNote] = useState({ title: "", content: "" });
function handleChange(event) {
const { name, value } = event.target;
setNote(prev => ({ ...prev, [name]: value }));
}
function submitNote(event) {
event.preventDefault();
onAdd(note); // ← 调用父组件的函数,传递数据
setNote({ title: "", content: "" }); // 清空表单
}
return (
<form onSubmit={submitNote}>
<input name="title" value={note.title} onChange={handleChange} placeholder="Title" />
<textarea name="content" value={note.content} onChange={handleChange} placeholder="Content" />
<button type="submit">Add</button>
</form>
);
}jsx
// 子组件 — Note
function Note({ id, title, content, onDelete }) {
function handleDelete() {
onDelete(id); // ← 调用父组件的删除函数,传递 ID
}
return (
<div className="note">
<h1>{title}</h1>
<p>{content}</p>
<button onClick={handleDelete}>🗑️</button>
</div>
);
}6.3 数据流总结
┌──────────────────┐
│ App (State) │
│ notes: [...] │
└──┬───────────┬───┘
props + fn ↓ ↓ props + fn
┌────────────────┐ ┌──────────────┐
│ CreateArea │ │ Note │
│ onAdd={fn} │ │ onDelete={fn}│
│ ────────── │ │ ────────── │
│ 调用 onAdd(data)│ │ 调用 onDelete(id)│
└────────────────┘ └──────────────┘
↑ 子→父 ↑ 子→父💡 React 的数据流是 单向 的:State 在父组件 → Props 向下传 → 子组件通过回调函数向上传数据。
七、不可变状态更新模式
7.1 为什么不能直接修改 State
javascript
// ❌ 直接修改 — React 不知道状态变了,不会重新渲染
const [items, setItems] = useState(["A", "B"]);
items.push("C"); // 修改了原数组,但 UI 不变
// ✅ 创建新副本 — React 检测到新引用,触发渲染
setItems([...items, "C"]);7.2 常见更新模式
| 操作 | 代码 |
|---|---|
| 添加元素 | setItems(prev => [...prev, newItem]) |
| 删除元素 | setItems(prev => prev.filter((_, i) => i !== id)) |
| 更新元素 | setItems(prev => prev.map((item, i) => i === id ? newItem : item)) |
| 更新对象字段 | setObj(prev => ({ ...prev, key: newValue })) |
| 清空 | setItems([]) |
| 递增数字 | setCount(prev => prev + 1) |
| 切换布尔 | setFlag(prev => !prev) |
八、Keeper App 完整实现
8.1 最终组件树
App
├── Header — 标题 "Keeper"
├── CreateArea — 标题 + 内容输入 + 添加按钮
├── Note[] — 笔记列表(标题 + 内容 + 删除按钮)
└── Footer — Copyright © {动态年份}8.2 核心状态管理
javascript
// App.jsx
function App() {
const [notes, setNotes] = useState([]);
function addNote(newNote) {
setNotes(prevNotes => [...prevNotes, newNote]);
}
function deleteNote(id) {
setNotes(prevNotes => prevNotes.filter((_, index) => index !== id));
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
{notes.map((noteItem, index) => (
<Note
key={index}
id={index}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
))}
<Footer />
</div>
);
}8.3 关键实现要点
| 要点 | 实现方式 |
|---|---|
| 新增笔记 | setNotes(prev => [...prev, newNote]) |
| 删除笔记 | setNotes(prev => prev.filter((_, i) => i !== id)) |
| 表单管理 | 对象 state + [name]: value 计算属性 |
| 防页面刷新 | event.preventDefault() |
| 子→父通信 | onAdd / onDelete 回调 props |
| 列表渲染 | notes.map() + key={index} |
九、Hook 规则
9.1 使用规则
| 规则 | 说明 |
|---|---|
| 只在顶层调用 | 不在 if/for/嵌套函数中调用 Hook |
| 只在函数组件中调用 | 不在普通 JS 函数或类组件中使用 |
| 命名约定 | Hook 名称必须以 use 开头 |
javascript
// ❌ 条件中使用 Hook
if (isLoggedIn) {
const [name, setName] = useState(""); // 报错!
}
// ❌ 循环中使用 Hook
for (let i = 0; i < 5; i++) {
const [val, setVal] = useState(0); // 报错!
}
// ✅ 始终在组件顶层
function App() {
const [name, setName] = useState("");
const [count, setCount] = useState(0);
// ... 在这里使用
}9.2 常用 Hooks 速览
| Hook | 用途 | 示例 |
|---|---|---|
useState | 管理组件状态 | const [val, setVal] = useState(0) |
useEffect | 副作用(API 调用、订阅等) | useEffect(() => {...}, [deps]) |
useContext | 跨组件共享数据 | const value = useContext(MyContext) |
useRef | 引用 DOM 元素 | const ref = useRef(null) |
useMemo | 缓存计算结果 | const val = useMemo(() => compute(), [deps]) |
useCallback | 缓存函数引用 | const fn = useCallback(() => {...}, [deps]) |
十、速查表
| 概念 | 语法/用法 |
|---|---|
| 声明状态 | const [val, setVal] = useState(initialVal) |
| 更新状态 | setVal(newVal) 或 setVal(prev => prev + 1) |
| 事件绑定 | <button onClick={handleClick}> |
| 带参事件 | <button onClick={() => handleClick(id)}> |
| onChange | <input value={val} onChange={e => setVal(e.target.value)} /> |
| 阻止默认 | event.preventDefault() |
| 对象状态更新 | setState(prev => ({ ...prev, [name]: value })) |
| 数组添加 | setArr(prev => [...prev, newItem]) |
| 数组删除 | setArr(prev => prev.filter((_, i) => i !== id)) |
| 子→父通信 | 父传 onAction={fn},子调用 props.onAction(data) |
| 受控输入 | <input value={state} onChange={handler} /> |
| Hook 规则 | 只在顶层调用,只在函数组件中使用 |