Skip to content

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 规则只在顶层调用,只在函数组件中使用

← 返回 Web 开发研究