一、概览与提问(SQ3R · Survey & Question)

SQ3R 第一步:快速浏览全貌,提出关键问题。

什么是 React?

React 是由 Meta(原 Facebook)开发并开源的 JavaScript UI 库,用于构建 Web 和原生用户界面。自 2013 年发布以来,React 已经成为全球最流行的前端框架之一。React 的核心理念是将 UI 拆分为独立的、可复用的组件(Component),每个组件管理自己的逻辑和外观。

当前最新版本是 React 19.2,引入了 Server Components、React Compiler、use API 等重大特性。React 不是一个大而全的框架——它专注于视图层,与路由、状态管理等库配合使用,形成完整的解决方案。

React 解决的核心问题:

  • UI 复杂性管理:通过组件化将复杂的界面拆解为可维护的小单元
  • 状态与视图同步:通过声明式编程,开发者只需描述"UI 应该是什么样",React 负责高效更新 DOM
  • 跨平台开发:同一套组件模型可以用于 Web(React DOM)和移动端(React Native)

核心问题

  • React 适合什么场景?——单页应用、交互密集的 Web 应用、需要跨平台的产品
  • React 与同类技术相比有什么优势?——生态成熟、社区庞大、灵活可组合、Meta 持续投入
  • 学习 React 需要什么基础?——HTML、CSS、JavaScript(ES6+)、基本的 DOM 知识

技术全景图

React 的技术架构可以概括为以下核心模块的协作:

React 应用
├── 描述 UI(Describing the UI)
│   ├── Components(组件)——UI 的基本构建单元
│   ├── JSX ——声明式标记语法
│   ├── Props ——组件间数据传递
│   └── 条件渲染 & 列表渲染
├── 添加交互性(Adding Interactivity)
│   ├── Events(事件处理)
│   ├── State(状态)——组件的记忆
│   └── Render & Commit(渲染与提交)
├── 状态管理(Managing State)
│   ├── 状态提升(Lifting State Up)
│   ├── Reducer ——复杂状态逻辑
│   ├── Context ——深层数据传递
│   └── Reducer + Context 组合模式
└── 逃逸舱(Escape Hatches)
    ├── Refs ——引用 DOM 和值
    ├── Effects ——与外部系统同步
    └── Custom Hooks ——逻辑复用

二、用最简单的话说清楚(费曼学习法)

费曼学习法核心理念:如果你不能用简单的语言解释一件事,说明你还没有真正理解它。

核心概念讲解

组件(Component)

组件就是 JavaScript 函数,返回你想要看到的界面。一个组件可以小到一个按钮,大到整个页面。

function MyButton() {
  return <button>I'm a button</button>;
}
 
// 在另一个组件中使用
export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton />
    </div>
  );
}

注意:组件名必须以大写字母开头(MyButton),HTML 标签用小写(button)。

JSX

JSX 是一种在 JavaScript 中写 HTML 的语法。它比 HTML 更严格:标签必须闭合(如 <br />),组件不能返回多个根标签,需要用 <div><>...</> 包裹。

function AboutPage() {
  return (
    <>
      <h1>About</h1>
      <p>
        Hello there.
        <br />
        How do you do?
      </p>
    </>
  );
}

用花括号 {} 可以在 JSX 中嵌入 JavaScript 表达式:

const user = { name: "Hedy Lamarr" };
return <h1>{user.name}</h1>;

Props(属性)

Props 是父组件传递给子组件的数据,就像函数的参数。

function MyButton({ count, onClick }) {
  return <button onClick={onClick}>Clicked {count} times</button>;
}
 
// 父组件传递 props
<MyButton count={count} onClick={handleClick} />;

State(状态)

State 是组件的"记忆"。用 useState Hook 声明一个状态变量。

import { useState } from "react";
 
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>;
}

useState 返回两个值:当前状态值(count)和更新函数(setCount)。每次调用 setCount,React 会重新渲染组件。

事件处理

事件处理函数在组件内部定义,通过 onEventName 属性绑定。

function MyButton() {
  function handleClick() {
    alert("You clicked me!");
  }
  return <button onClick={handleClick}>Click me</button>;
}

注意:onClick={handleClick} 末尾没有括号——你传递的是函数本身,不是调用结果。React 会在用户点击时调用它。

条件渲染

React 没有特殊的条件语法,直接使用 JavaScript:

// if...else
let content;
if (isLoggedIn) {
  content = <AdminPanel />;
} else {
  content = <LoginForm />;
}
return <div>{content}</div>;
 
// 三元表达式(可以在 JSX 中使用)
<div>{isLoggedIn ? <AdminPanel /> : <LoginForm />}</div>
 
// 逻辑与(不需要 else 分支时)
<div>{isLoggedIn && <AdminPanel />}</div>

列表渲染

使用 JavaScript 的 map() 函数渲染列表,每个列表项需要唯一的 key

const products = [
  { title: "Cabbage", id: 1 },
  { title: "Garlic", id: 2 },
  { title: "Apple", id: 3 },
];
 
const listItems = products.map((product) => <li key={product.id}>{product.title}</li>);
return <ul>{listItems}</ul>;

key 帮助 React 识别列表中哪些项发生了变化(插入、删除、重排)。

类比与比喻

  • 组件 = 乐高积木:每块积木有独立的形状和功能,可以自由组合拼装成更大的作品。一个按钮是一块积木,一个页面是许多积木的组合。
  • Props = 快递包裹:父组件通过 props 给子组件"寄包裹"。子组件收到包裹后使用里面的东西,但不能自己修改包裹内容。
  • State = 组件的记事本:每个组件有自己的小本子,用来记录会变化的信息。本子上的内容变了,界面就自动更新。
  • JSX = HTML 的超集:就像在 HTML 里可以直接写 JavaScript 一样,用花括号 {} 把 JavaScript "嵌入"到标记语言中。
  • Hooks = 插件接口:Hooks 就像 USB 接口,让你把 React 的各种能力(状态管理、副作用、引用)"插"到组件里。
  • Context = 室内广播:如果说 Props 是快递员一层层递送包裹,那 Context 就像房间里的广播——顶层组件广播一条消息,下面任何层级的组件都能听到。

常见误解澄清

  1. "React 是一个 MVC 框架"——不是。React 只是 V(视图层)。路由、数据获取等需要其他库配合。
  2. "JSX 就是 HTML"——不是。JSX 是 JavaScript 的语法扩展,更严格(标签必须闭合、className 代替 class)。
  3. "State 变了 DOM 就立刻更新"——不是。React 会批量处理状态更新,在合适的时机统一更新 DOM。
  4. "每次渲染都会重建所有 DOM"——不是。React 通过 Virtual DOM 进行 diff 算法比较,只更新实际变化的部分。
  5. "Hooks 可以在 if/for 中调用"——不可以。Hooks 必须在组件顶层调用,不能放在条件语句、循环或嵌套函数中。
  6. "useEffect 就是 Vue 的 watch"——不完全正确。useEffect 是在渲染提交到屏幕之后运行的,用于与外部系统同步,不是简单的数据监听器。
  7. "Context 可以替代所有状态管理"——不可以。Context 适合"远距离"传递数据,但滥用会导致不必要的重渲染。

三、锥形深入(西蒙学习法)

西蒙学习法:集中精力、目标导向、锥形深入——从核心开始,逐步扩展到周边。

第一层:核心基础

1. useState —— 状态管理

useState 是最基础的 Hook,为组件添加"记忆"。

import { useState } from "react";
 
function Form() {
  const [firstName, setFirstName] = useState("Mary");
  const [lastName, setLastName] = useState("Poppins");
 
  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }
 
  return (
    <>
      <label>
        First name:
        <input value={firstName} onChange={handleFirstNameChange} />
      </label>
      <p>
        <b>
          Good morning, {firstName} {lastName}.
        </b>
      </p>
    </>
  );
}

要点

  • useState(initialValue) 返回 [state, setState]
  • 调用 setState 会触发重新渲染
  • 状态更新可能是异步的,使用函数式更新保证基于最新值:setCount(c => c + 1)

2. useEffect —— 副作用处理

useEffect 用于在渲染之后与外部系统同步。

import { useEffect, useRef } from "react";
 
function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);
 
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]); // 只在 isPlaying 变化时重新运行
 
  return <video ref={ref} src={src} loop playsInline />;
}

三步写 Effect

  1. 声明 Effect(useEffect(() => { ... }))
  2. 指定依赖数组([dep1, dep2]
  3. 如有需要,添加清理函数(return () => { ... }

3. Props 与状态提升

当多个组件需要共享状态时,将状态提升到它们的最近公共父组件。

import { useState } from "react";
 
export default function MyApp() {
  const [count, setCount] = useState(0);
 
  function handleClick() {
    setCount(count + 1);
  }
 
  return (
    <div>
      <h1>Counters that update together</h1>
      <MyButton count={count} onClick={handleClick} />
      <MyButton count={count} onClick={handleClick} />
    </div>
  );
}
 
function MyButton({ count, onClick }) {
  return <button onClick={onClick}>Clicked {count} times</button>;
}

4. useRef —— 引用值

useRef 用于保存不触发重新渲染的值,或引用 DOM 元素。

import { useRef } from "react";
 
function MyInput({ value, onChange }) {
  const ref = useRef(null);
 
  function focusInput() {
    ref.current.focus();
  }
 
  return <input ref={ref} value={value} onChange={onChange} />;
}

useState 的区别:修改 ref.current 不会触发重新渲染。

5. 事件处理

function Form() {
  function handleSubmit(e) {
    e.preventDefault();
    console.log("Form submitted");
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

6. 条件渲染与列表渲染

function ProductList({ products, filterText, inStockOnly }) {
  const rows = [];
  let lastCategory = null;
 
  products.forEach((product) => {
    // 过滤逻辑
    if (product.name.toLowerCase().indexOf(filterText.toLowerCase()) === -1) {
      return;
    }
    if (inStockOnly && !product.stocked) {
      return;
    }
    // 条件渲染分类标题
    if (product.category !== lastCategory) {
      rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
    }
    rows.push(<ProductRow product={product} key={product.name} />);
    lastCategory = product.category;
  });
 
  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

7. 更新对象和数组

React 中状态是不可变的,必须创建新的对象/数组来触发更新。

import { useState } from "react";
 
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [text, setText] = useState("");
 
  function addTodo() {
    // 创建新数组,而非修改原数组
    setTodos([...todos, text]);
    setText("");
  }
 
  function removeTodo(index) {
    setTodos(todos.filter((_, i) => i !== index));
  }
 
  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map((todo, i) => (
          <li key={i}>
            {todo}
            <button onClick={() => removeTodo(i)}>Delete</button>
          </li>
        ))}
      </ul>
    </>
  );
}

第二层:进阶用法

1. useContext —— 深层数据传递

Context 解决了"prop drilling"问题——无需层层传递 props。

// 1. 创建 Context
import { createContext, useContext } from "react";
 
const LevelContext = createContext(1);
 
// 2. 在子组件中使用
function Heading({ children }) {
  const level = useContext(LevelContext);
  const Tag = `h${level}`;
  return <Tag>{children}</Tag>;
}
 
// 3. 在父组件中提供
function Section({ level, children }) {
  return <LevelContext value={level}>{children}</LevelContext>;
}
 
// 使用:Section 自动传递 level 给所有嵌套的 Heading
function Page() {
  return (
    <Section level={1}>
      <Heading>Title</Heading>
      <Section level={2}>
        <Heading>Heading</Heading>
        <Heading>Heading</Heading>
      </Section>
    </Section>
  );
}

典型应用场景:主题切换、当前用户、国际化语言设置、路由信息。

2. useReducer —— 复杂状态逻辑

当状态逻辑复杂时,useReduceruseState 更清晰。

import { useReducer } from "react";
 
function reducer(state, action) {
  switch (action.type) {
    case "incremented":
      return { count: state.count + 1 };
    case "decremented":
      return { count: state.count - 1 };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
 
function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <button onClick={() => dispatch({ type: "decremented" })}>-</button>
      {state.count}
      <button onClick={() => dispatch({ type: "incremented" })}>+</button>
    </>
  );
}

3. useCallback 与 useMemo —— 性能优化

import { useState, useCallback, useMemo } from "react";
 
function ProductPage({ products }) {
  const [filterText, setFilterText] = useState("");
  const [isLarge, setIsLarge] = useState(false);
 
  // useMemo:缓存计算结果
  const filteredProducts = useMemo(() => {
    return products.filter((p) => p.name.toLowerCase().includes(filterText.toLowerCase()));
  }, [products, filterText]);
 
  // useCallback:缓存函数引用
  const handleFilterChange = useCallback((e) => {
    setFilterText(e.target.value);
  }, []);
 
  return (
    <>
      <input value={filterText} onChange={handleFilterChange} />
      <ProductList products={filteredProducts} />
    </>
  );
}

注意:不要过度使用。React 19 配合 React Compiler 可以自动处理大部分优化。

4. 自定义 Hook

自定义 Hook 用于在组件间复用有状态逻辑。

// useOnlineStatus.js
import { useState, useEffect } from "react";
 
export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
 
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
 
    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);
 
    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);
 
  return isOnline;
}
 
// 在多个组件中使用
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? "Online" : "Disconnected"}</h1>;
}
 
function SaveButton() {
  const isOnline = useOnlineStatus();
  return <button disabled={!isOnline}>{isOnline ? "Save progress" : "Reconnecting..."}</button>;
}

命名规则:自定义 Hook 必须以 use 开头(如 useOnlineStatus),这样 React 的 lint 规则才能正确检查其使用方式。

5. Effect 的正确使用模式

Effect 用于与外部系统同步,不是用来响应状态变化的通用工具。

// 正确:连接到聊天服务器
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect(); // 清理函数
  }, [roomId]); // 只在 roomId 变化时重连
}
 
// 错误:不需要 Effect 来根据其他 state 计算 state
// 错误做法:
useEffect(() => {
  setFullName(firstName + " " + lastName);
}, [firstName, lastName]);
 
// 正确做法:直接在渲染时计算
const fullName = firstName + " " + lastName;

Effect 清理的关键模式

  • 事件订阅 → addEventListener / removeEventListener
  • 数据获取 → 设置 ignore 标志忽略过时响应
  • 动画 → 重置到初始值
  • 定时器 → setTimeout / clearTimeout

第三层:深度解析

1. 渲染机制:Render & Commit

React 更新 UI 的过程分三步:

  1. 触发渲染(Trigger):调用 setState 或首次渲染
  2. 渲染(Render):React 调用组件函数,计算新的 JSX(Virtual DOM)
  3. 提交(Commit):React 将变化应用到真实 DOM

State 是快照:每次渲染都有自己固定的 state 值。事件处理函数和 Effect 中捕获的 state 值属于那次渲染,不会改变。

function Counter() {
  const [count, setCount] = useState(0);
 
  function handleAlertClick() {
    setTimeout(() => {
      alert("You clicked on: " + count); // 显示的是点击时的 count,不是当前的
    }, 3000);
  }
 
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}

2. 状态更新的批处理与队列

React 会批量处理状态更新。同一事件处理函数中的多个 setState 只会触发一次渲染。

function handleClick() {
  setCount(count + 1); // 此时 count 还是旧值
  setCount(count + 1); // 还是基于旧值,结果只 +1
}
 
// 正确做法:使用函数式更新
function handleClick() {
  setCount((c) => c + 1); // c 是最新的待处理值
  setCount((c) => c + 1); // 基于上一个更新,结果 +2
}

3. React 编译器(React Compiler)

React 19 引入了 React Compiler,这是一个构建时优化工具,自动对组件和值进行 memoization:

  • 自动识别需要缓存计算的地方
  • 减少 useMemouseCallback 的手动使用
  • 可以渐进式采用,与现有代码兼容
  • 通过 @babel/plugin-transform-react-compiler 集成

4. use API

React 19 新增的 use API 可以在渲染期间读取 Promise 和 Context:

import { use, Suspense } from "react";
 
function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}
 
export function MessageContainer({ messagePromise }) {
  return (
    <Suspense fallback={<p>Downloading message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

5. Thinking in React —— React 思维方式

构建 React 应用的五个步骤:

  1. 将 UI 拆分为组件层级:根据数据模型和设计稿划分组件
  2. 构建静态版本:先不考虑交互,用 props 传递数据构建 UI
  3. 确定最小状态集合:遵循 DRY 原则,找出真正需要的状态
  4. 确定状态的位置:将状态放在使用它的组件的最近公共父组件中
  5. 添加反向数据流:子组件通过回调函数更新父组件的状态

6. Reducer + Context 组合模式

这是 React 管理复杂应用状态的模式:

import { createContext, useContext, useReducer } from "react";
 
// 1. 创建 Context
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
 
// 2. 定义 Reducer
function tasksReducer(tasks, action) {
  switch (action.type) {
    case "added":
      return [...tasks, { id: action.id, text: action.text }];
    case "deleted":
      return tasks.filter((t) => t.id !== action.id);
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
 
// 3. Provider 组件
export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
  return (
    <TasksContext value={tasks}>
      <TasksDispatchContext value={dispatch}>{children}</TasksDispatchContext>
    </TasksContext>
  );
}
 
// 4. 自定义 Hook 供子组件使用
export function useTasks() {
  return useContext(TasksContext);
}
export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

7. 组件纯净性与 Rules of React

React 有三条核心规则:

  1. 组件和 Hook 必须是纯函数:相同的输入产生相同的输出,不产生副作用
  2. React 负责调用组件和 Hook:不要手动调用组件函数
  3. Hook 的使用规则:只在组件或 Hook 的顶层调用,不能在条件语句或循环中调用

四、要点笔记(康奈尔笔记法)

康奈尔笔记法:将笔记分为线索栏、笔记栏和总结栏,便于复习和检索。

关键概念速查表

线索/关键词详细笔记
Component返回 JSX 的 JavaScript 函数,名称大写开头
JSXJavaScript 的标记语法扩展,用花括号嵌入表达式
Props父传子的只读数据,类似函数参数
State组件内部的可变数据,用 useState 声明
Hooksuse 开头的函数,只能在组件顶层调用
Virtual DOMReact 内部的轻量级 DOM 表示,用于 diff 计算
Re-renderstate/props 变化时组件函数重新执行
Render & Commit触发 -> 计算 JSX -> 应用到真实 DOM
State Snapshot每次渲染的 state 值固定不变
Lifting State Up将共享状态移到最近公共父组件
Effect渲染后执行,用于与外部系统同步
CleanupEffect 返回的清理函数,在下次 Effect 运行前和卸载时调用
Context父组件向任意深度的子组件传递数据
Reducerdispatch(action) 管理复杂状态逻辑
Custom Hookuse 开头的自定义函数,复用有状态逻辑
Key列表渲染中的唯一标识,帮助 React 追踪元素
React Compiler构建时自动 memoization 的优化工具
use APIReact 19 新增,在渲染中读取 Promise/Context

核心 API 速查

API / Hook用途示例
useState声明状态变量const [val, setVal] = useState(0)
useEffect渲染后执行副作用useEffect(() => { ... }, [dep])
useRef引用 DOM 或保存不触发渲染的值const ref = useRef(null)
useContext读取 Context 值const value = useContext(MyContext)
useReducer复杂状态管理const [state, dispatch] = useReducer(reducer, init)
useCallback缓存函数引用const fn = useCallback(() => {}, [deps])
useMemo缓存计算结果const val = useMemo(() => compute(a), [a])
createContext创建 Contextconst Ctx = createContext(defaultValue)
use在渲染中读取 Promiseconst data = use(promise)
useSyncExternalStore订阅外部数据源const val = useSyncExternalStore(subscribe, getSnapshot)
useEffectEventEffect 中的事件处理器(不加入依赖)const onMsg = useEffectEvent(handler)

本节总结

React 的核心思想是组件化声明式 UI。组件通过 Props 接收数据、通过 State 管理内部状态、通过 Context 进行远距离数据传递。Hooks(useStateuseEffectuseRef 等)是组件与 React 能力交互的唯一入口。Effect 用于与外部系统同步,自定义 Hook 用于复用有状态逻辑。React 19 的 React Compiler 和 use API 代表了框架的发展方向——更少的样板代码,更自动化的优化。

五、复习与实践(SQ3R · Recite & Review)

SQ3R 最后两步:复述核心要点,通过实践巩固理解。

核心要点回顾

  1. React 组件是返回 JSX 的 JavaScript 函数,组件名必须大写开头
  2. JSX 中用花括号 {} 嵌入 JavaScript 表达式
  3. Props 是父传子的只读数据流,State 是组件内部的可变状态
  4. useState 返回 [value, setter],调用 setter 触发重新渲染
  5. 状态提升(Lifting State Up)是共享组件状态的标准模式
  6. 列表渲染必须使用唯一的 key 属性
  7. useEffect 用于与外部系统同步,必须指定依赖数组
  8. Effect 需要清理函数来防止资源泄漏(订阅、连接、定时器)
  9. Context 解决 prop drilling 问题,但不应该替代所有状态传递
  10. 自定义 Hook 以 use 开头,用于在组件间复用有状态逻辑
  11. useReducer 适合复杂状态逻辑,配合 Context 可形成全局状态管理方案
  12. React 19 的 React Compiler 自动处理 memoization,减少手动优化
  13. 渲染过程:Trigger -> Render(计算 JSX)-> Commit(更新 DOM)
  14. State 在每次渲染中是固定的快照,闭包捕获的是当时渲染的值
  15. React 的三条规则:组件纯净、React 控制调用、Hook 规则

动手练习

  1. 静态个人名片页:创建一个包含头像、姓名、简介的组件。练习组件创建、Props 传递和 JSX 基础。

  2. 可搜索的产品列表:按照 "Thinking in React" 教程,实现一个带搜索过滤和库存筛选的产品表格。练习状态提升、条件渲染和列表渲染。

  3. 聊天室应用:创建一个连接到模拟服务器的聊天组件。练习 useEffect 的声明、依赖数组和清理函数。

  4. 主题切换系统:用 Context 实现明/暗主题切换,让所有嵌套组件自动适配。练习 createContextuseContext 和 Provider。

  5. 任务管理系统:用 Reducer + Context 模式实现一个 Todo 应用,支持添加、删除、标记完成。练习 useReducer、自定义 Hook 和 Context 组合。

常见陷阱

  • 忘记依赖数组useEffect 不传依赖数组会在每次渲染后运行,可能导致无限循环

    // 错误:无限循环
    useEffect(() => {
      setCount(count + 1);
    });
     
    // 正确:明确依赖
    useEffect(() => {
      setCount((c) => c + 1);
    }, []);
  • 直接修改状态对象:必须创建新对象/数组

    // 错误
    state.items.push(newItem);
    setState(state);
     
    // 正确
    setState({ ...state, items: [...state.items, newItem] });
  • 在 Effect 中做不该做的事:购买商品、提交表单等用户触发的操作应该放在事件处理器中,不是 Effect

  • 使用索引作为 key:在列表会重排、插入、删除时,用索引作 key 会导致渲染错误。应使用稳定的唯一 ID

  • 过度使用 Context:Context 值变化时所有消费者都会重渲染。对于高频变化的数据,考虑使用状态管理库或将状态限定在更小的范围内

延伸阅读