Article

前端框架 React

更新于:2026-07-10

第一章:React 基础与开发环境

1.1 什么是 React

概念名称说明注意事项
React一个用于构建用户界面的 JavaScript 库,由 Facebook(现 Meta)开发并开源。React 专注于应用的视图层(View),采用组件化架构,使开发者能够高效地构建可复用的 UI 组件。React 不是框架(如 Angular),它只负责 UI 渲染,其他功能(如路由、状态管理)需依赖生态库。
用户界面(UI)指用户与应用程序交互的视觉部分,如按钮、表单、列表等。React 的核心任务是高效地渲染和更新这些 UI 元素。React 可用于 Web(React DOM)、移动端(React Native)、桌面端(Electron)等多种平台。
声明式编程React 采用声明式范式:开发者描述”UI 应该是什么样”,而非”如何一步步构建 UI”。React 负责将状态变化自动映射为 DOM 更新。相比命令式编程(如直接操作 DOM),声明式更易读、易维护,减少出错概率。

1.2 React 的核心特点

特点说明注意事项
声明式(Declarative)开发者通过 JSX 描述 UI 在不同状态下的样子,React 自动处理 DOM 更新。例如:<Button disabled={isLoading} />,React 根据 isLoading 值决定是否禁用按钮。减少手动 DOM 操作,提升开发效率和代码可维护性。
组件化(Component-Based)UI 被拆分为独立、可复用的组件。每个组件管理自己的状态和逻辑,可嵌套组合形成复杂界面。例如:HeaderSidebarComment 都是组件。遵循单一职责原则,组件应尽量小而专注,便于测试和复用。
虚拟 DOM(Virtual DOM)React 在内存中维护一个轻量级的 DOM 表示(即虚拟 DOM)。当状态变化时,React 先在虚拟 DOM 上计算差异(diffing),再批量更新真实 DOM,提升性能。虚拟 DOM 不是直接操作真实 DOM,而是通过高效的 diff 算法最小化 DOM 操作次数。
单向数据流(Unidirectional Data Flow)数据从父组件通过 props 单向传递给子组件。子组件不能直接修改 props,只能通过回调函数通知父组件更新状态。保证数据流动清晰,便于调试和追踪状态变化。避免数据”双向绑定”带来的复杂性。

1.3 React 生态概览

生态工具说明注意事项
React DOM用于在 Web 浏览器中渲染 React 组件。提供 ReactDOM.render() 方法将 React 元素挂载到 DOM 节点。现代 React 使用 createRoot(React 18+)替代 render,支持并发渲染。
React Native使用 React 构建原生移动应用(iOS 和 Android)。使用原生组件而非 Web 标签,实现接近原生性能的体验。学习一次,可跨平台开发,但需了解平台特定 API 和样式差异。
Next.jsReact 的服务端渲染(SSR)和静态生成(SSG)框架,支持 SEO 友好、高性能的 Web 应用。内置路由、API 路由、图像优化等功能。适合需要 SEO 或高性能加载的项目,如官网、博客、电商平台。
Remix全栈 Web 框架,强调 Web 原生体验和快速加载。基于 React Router,支持数据加载、表单处理等。更适合复杂的数据驱动应用,学习曲线略高于 Next.js。
Gatsby基于 React 的静态站点生成器,适合构建文档、博客、营销页面等。从多种数据源预生成静态页面。构建速度快,部署简单,适合内容为主的网站。
Redux / Zustand状态管理库。Redux 用于大型应用的全局状态管理;Zustand 是轻量级替代方案,API 更简洁。小型项目可直接使用 useStateuseContext,避免过度设计。

1.4 使用 Create React App 搭建项目

方法/命令语法用途代码示例注意事项
create-react-appnpx create-react-app <project-name>创建一个新的 React 项目,包含默认配置、开发服务器、构建脚本等。npx create-react-app my-app推荐使用 npx,无需全局安装。<project-name> 不能包含大写字母或特殊字符。
--templatenpx create-react-app <project-name> --template [template-name]使用指定模板创建项目,如 TypeScript、Redux 等。npx create-react-app my-app --template typescript常用模板:typescriptcra-template-reduxcra-template-typescript-redux

1.5 项目结构解析与开发服务器启动

文件/目录说明注意事项
public/index.html应用的 HTML 入口文件。React 组件将挂载到 <div id="root"></div>可在此添加 meta 标签、引入 CDN 资源等。
src/index.jsJavaScript 入口文件。调用 ReactDOM.createRoot 并渲染根组件 <App /> 到 DOM。React 18 使用 createRoot 启用并发模式。
src/App.js根组件,初始 UI 内容。可被修改或替换。通常作为其他组件的容器。
src/components/(建议)存放可复用的 UI 组件。CRA 不自动生成,需手动创建。良好的项目结构应分离组件、页面、工具等。
package.json项目配置文件,包含依赖、脚本、版本等信息。所有 npm 脚本在此定义。
node_modules/存放项目依赖包。由 npm install 自动生成。不应提交到版本控制(.gitignore 已包含)。
package-lock.json锁定依赖版本,确保安装一致性。应提交到 Git,保证团队环境一致。
npm 脚本语法用途代码示例注意事项
npm startnpm run start启动开发服务器,默认在 http://localhost:3000 打开应用。npm start开发服务器支持热重载(HMR),修改代码后自动刷新。
npm run buildnpm run build构建生产环境优化的静态文件,输出到 build/ 目录。npm run build用于部署,文件已压缩、优化。
npm testnpm run test启动测试运行器,执行项目中的测试文件。npm testCRA 使用 Jest + React Testing Library。
npm run ejectnpm run eject将所有配置暴露出来,不再受 CRA 封装限制。npm run eject⚠️ 不可逆操作!仅在需要深度定制配置时使用。

1.6 最小可用实现 ToDoList

文件路径主要用途用到的主要接口/语法
public/index.htmlHTML 页面模板,React 应用挂载点<div id="root"></div>(React 挂载点)
src/index.js应用入口文件,启动 React 并渲染根组件ReactDOM.createRoot(document.getElementById('root')).render(<BrowserRouter><App /></BrowserRouter>);
src/App.js主应用组件,定义路由和整体布局状态管理,内部的标签、组件、值都会作为参数,传递给 Provider 定义时的函数(一般在 context 目录下定义)
src/App.css全局样式或 App 组件专用样式CSS 样式规则、类选择器
src/context/TodoContext.js管理全局待办事项状态useContext + useReducer 在组件间完成状态共享
src/pages/AllTodos.js显示所有待办事项的页面组件组合,解析数据,渲染到页面
src/pages/ActiveTodos.js显示未完成的待办事项的页面组件组合,解析数据,渲染到页面
src/pages/CompletedTodos.js显示已完成的待办事项的页面组件组合,解析数据,渲染到页面
src/components/TodoForm.js表单组件,用于添加新任务
src/components/TodoItem.js单项组件,支持完成/删除
src/components/TodoList.js列表组件,渲染多个 TodoItem

注意:组件中 return 内容和 HTML 代码的区别

对比项HTMLJSX(React 中的”类 HTML”)
本质标记语言,浏览器直接解析JavaScript 语法扩展,需编译
文件扩展名.html.jsx.js
属性命名class="xxx"className="xxx"class 是 JS 关键字)
事件处理onclick="handle()"onClick={handle}(驼峰命名,handle 函数绑定给 onClick 变量)
嵌入逻辑需用 <script>可直接用 {} 嵌入 JS 表达式
闭合标签某些可省略(如 <p>必须闭合:<img /><br />
返回内容可以有多个根节点返回一个根元素(如 <div>…</div>),或使用 <>...</> 包含多个 <div>

项目结构:

todo-app/
├── public/
│   └── index.html
└── src/
    ├── components/
    │   ├── TodoForm.js
    │   ├── TodoItem.js
    │   └── TodoList.js
    ├── context/
    │   └── TodoContext.js
    ├── pages/
    │   ├── AllTodos.js
    │   ├── CompletedTodos.js
    │   └── ActiveTodos.js
    ├── App.js
    ├── index.js
    └── App.css

(1)public/index.html - 应用的 HTML 入口文件:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>最小可用 React 项目</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

(2)src/index.js - 应用的 JavaScript 入口点:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './App.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

(3)src/context/TodoContext.js - 使用 Context API 进行状态管理:

import React, { createContext, useContext, useReducer } from 'react';

// 定义初始状态
const initialState = {
  todos: [
    { id: 1, text: '学习 React', completed: false },
    { id: 2, text: '完成项目', completed: true }
  ]
};

// 定义 action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const DELETE_TODO = 'DELETE_TODO';

// Reducer 函数(处理状态更新)
function todoReducer(state, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(), // 简单的 ID 生成
            text: action.payload,
            completed: false
          }
        ]
      };
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    default:
      return state;
  }
}

// 创建 Context
const TodoContext = createContext();

// 自定义 Hook,方便在组件中使用 Context
export function useTodo() {
  const context = useContext(TodoContext);
  if (!context) {
    throw new Error('useTodo must be used within a TodoProvider');
  }
  return context;
}

// Provider 组件
export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  // 将 dispatch 封装成更易用的函数
  const addTodo = (text) => dispatch({ type: ADD_TODO, payload: text });
  const toggleTodo = (id) => dispatch({ type: TOGGLE_TODO, payload: id });
  const deleteTodo = (id) => dispatch({ type: DELETE_TODO, payload: id });

  return (
    <TodoContext.Provider value={{ ...state, addTodo, toggleTodo, deleteTodo }}>
      {children}
    </TodoContext.Provider>
  );
}

(4)src/components/TodoForm.js - 管理 state(输入框内容),处理事件系统(表单提交和输入变化):

import React, { useState } from 'react';

function TodoForm({ onAdd }) {
  // 使用 useState Hook 管理组件内部状态
  const [input, setInput] = useState('');

  // 处理表单提交事件
  const handleSubmit = (e) => {
    e.preventDefault(); // 阻止默认提交行为
    if (input.trim() !== '') {
      onAdd(input); // 调用父组件传递的函数
      setInput('');  // 清空输入框
    }
  };

  // 处理输入框变化事件
  const handleChange = (e) => {
    setInput(e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={input}
        onChange={handleChange}
        placeholder="添加新的待办事项..."
      />
      <button type="submit">添加</button>
    </form>
  );
}

export default TodoForm;

(5)src/components/TodoItem.js - 处理事件系统(点击复选框和删除按钮):

import React from 'react';

function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)} // 事件处理
      />
      <span>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </li>
  );
}

export default TodoItem;

(6)src/components/TodoList.js - 使用 Hook(useEffect)模拟副作用(例如,当列表更新时在控制台打印):

import React, { useEffect } from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos, onToggle, onDelete }) {
  // 使用 useEffect Hook 处理副作用
  useEffect(() => {
    console.log(`当前待办事项数量: ${todos.length}`);
    // 模拟:如果列表为空,打印提示
    if (todos.length === 0) {
      console.log('待办事项列表为空!');
    }
    // 清理函数(可选)
    return () => {
      console.log('TodoList 组件即将卸载或重新渲染');
    };
  }, [todos]); // 依赖数组:仅在 todos 变化时执行

  // 使用 map 方法渲染列表(体现组件复用)
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onToggle={onToggle}
          onDelete={onDelete}
        />
      ))}
    </ul>
  );
}

export default TodoList;

(7)src/pages/AllTodos.jsActiveTodos.jsCompletedTodos.js - 路由对应的页面组件,使用 useTodo 自定义 Hook 来访问全局状态:

AllTodos.js

import React from 'react';
import { useTodo } from '../context/TodoContext';
import TodoList from '../components/TodoList';
import TodoForm from '../components/TodoForm';

function AllTodos() {
  const { todos, addTodo, toggleTodo, deleteTodo } = useTodo();

  return (
    <div>
      <h2>所有待办事项</h2>
      <TodoForm onAdd={addTodo} />
      <TodoList
        todos={todos}
        onToggle={toggleTodo}
        onDelete={deleteTodo}
      />
    </div>
  );
}

export default AllTodos;

ActiveTodos.js

import React from 'react';
import { useTodo } from '../context/TodoContext';
import TodoList from '../components/TodoList';

function ActiveTodos() {
  const { todos, toggleTodo, deleteTodo } = useTodo();
  const activeTodos = todos.filter(todo => !todo.completed);

  return (
    <div>
      <h2>未完成</h2>
      <TodoList
        todos={activeTodos}
        onToggle={toggleTodo}
        onDelete={deleteTodo}
      />
    </div>
  );
}

export default ActiveTodos;

CompletedTodos.js

import React from 'react';
import { useTodo } from '../context/TodoContext';
import TodoList from '../components/TodoList';

function CompletedTodos() {
  const { todos, toggleTodo, deleteTodo } = useTodo();
  const completedTodos = todos.filter(todo => todo.completed);

  return (
    <div>
      <h2>已完成</h2>
      <TodoList
        todos={completedTodos}
        onToggle={toggleTodo}
        onDelete={deleteTodo}
      />
    </div>
  );
}

export default CompletedTodos;

(8)src/App.js - 应用的根组件,使用 react-router-dom 进行路由,并使用 TodoProvider 提供状态管理:

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import { TodoProvider } from './context/TodoContext';
import AllTodos from './pages/AllTodos';
import ActiveTodos from './pages/ActiveTodos';
import CompletedTodos from './pages/CompletedTodos';

function App() {
  return (
    <TodoProvider> {/* 状态管理:提供全局状态 */}
      <div className="App">
        <h1>待办事项列表</h1>
        <nav>
          <Link to="/">所有</Link> |
          <Link to="/active">未完成</Link> |
          <Link to="/completed">已完成</Link>
        </nav>

        {/* 路由 */}
        <Routes>
          <Route path="/" element={<AllTodos />} />
          <Route path="/active" element={<ActiveTodos />} />
          <Route path="/completed" element={<CompletedTodos />} />
        </Routes>
      </div>
    </TodoProvider>
  );
}

export default App;

(9)src/App.css(可选):

.App {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

nav {
  margin: 20px 0;
}

form {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

input[type="text"] {
  flex: 1;
  padding: 8px;
}

button {
  padding: 8px 16px;
  cursor: pointer;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid #eee;
}

li button {
  margin-left: auto;
  background: #ff4d4f;
  color: white;
  border: none;
}

运行步骤:

# 创建项目
npx create-react-app todo-app --template javascript
cd todo-app

# 安装路由
npm install react-router-dom

# 启动
npm start

第二章:JSX 语法基础

2.1 什么是 JSX

概念名称说明注意事项
JSX(JavaScript XML)一种 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的结构。React 推荐使用 JSX 来描述 UI。JSX 不是字符串,也不是 HTML,而是会被编译为 React.createElement() 调用。
语法糖JSX 是 React.createElement() 的语法糖,提升代码可读性和开发效率。浏览器不能直接运行 JSX,需通过 Babel 等工具编译为纯 JavaScript。
必须引入 React即使不显式使用 React 变量,使用 JSX 时也必须导入 react,因为 JSX 会被转换为 React.createElement() 调用。在 React 17+,新 JSX 转换允许不显式导入 React,但建议仍保留以兼容性和清晰性。

2.2 JSX 与 HTML 的异同

对比项JSXHTML注意事项
标签闭合自闭合标签必须闭合,如 <img /><input />可省略闭合,如 <img><input>JSX 遵循 XML 规范,所有标签必须闭合。
属性命名使用驼峰命名法,如 classNameonClick使用小写,如 classonclickclassclassNameforhtmlFor(因 for 是 JS 保留字)。
布尔属性<input disabled={true} /><input disabled /><input disabled>省略值时默认为 true,JSX 中不能写 disabled="disabled"
注释{/* 注释内容 */}<!-- 注释内容 -->JSX 注释必须用 {} 包裹,且使用 /* */ 语法。

2.3 在 JSX 中嵌入表达式

方法语法用途代码示例注意事项
嵌入表达式{expression}在 JSX 中插入任意 JavaScript 表达式(变量、函数调用、运算等)。function Greeting() { const name = "Alice"; return (<p>Hello, {name}!</p>); }只能使用表达式,不能使用语句(如 iffor)。可用三元运算符或立即执行函数替代。
嵌入函数调用{functionCall()}执行函数并插入返回值。见下方示例
嵌入对象(有限){object}仅可嵌入能转为字符串的对象(如日期),普通对象会报错。function ShowDate() { return <p>Today: {new Date().toLocaleDateString()}</p>; }不能直接插入普通对象 {name: "John"},会报错:Objects are not valid as a React child

嵌入函数调用示例:

function formatUserName(firstName, lastName) {
  return `${firstName} ${lastName}`.trim();
}

function UserProfile({ user }) {
  return (
    <div>
      <h2>User: {formatUserName(user.firstName, user.lastName)}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
}

// 使用示例
function App() {
  const currentUser = {
    firstName: "John",
    lastName: "Doe",
    email: "john.doe@example.com"
  };
  return <UserProfile user={currentUser} />;
}

export default App;

2.4 JSX 属性与动态值

方法语法用途代码示例注意事项
静态属性<Element attr="value" />设置固定属性值。function StaticImage() { return <img src="/logo.png" alt="Logo" />; }适用于不变化的属性。
动态属性<Element attr={value} />使用 {} 插入变量或表达式作为属性值。function DynamicImage({ url, desc }) { return <img src={url} alt={desc} />; }所有非字符串属性都必须用 {} 包裹。
布尔属性<Element disabled={true} />控制布尔属性的开关。function ToggleButton({ isDisabled }) { return <button disabled={isDisabled}>Click me</button>; }使用变量控制,避免硬编码。
展开属性<Element {...props} />将对象的所有属性批量传递给组件。function ForwardProps() { const imgProps = { src: "/photo.jpg", alt: "A photo", width: 200 }; return <img {...imgProps} />; }常用于高阶组件或属性转发,注意避免覆盖显式属性。

2.5 JSX 中的样式处理(内联样式、className)

方法语法用途代码示例注意事项
内联样式style={{ key: value }}使用 JavaScript 对象设置内联样式,属性名使用驼峰命名。见下方示例样式值为字符串或数字(如 fontSize: 24 等价于 24px)。不支持伪类、媒体查询。
classNameclassName="class-name"使用 CSS 类名应用外部样式表。见下方示例推荐方式,样式与逻辑分离,支持复杂 CSS 规则。
动态类名className={condition ? 'active' : ''}根据状态动态切换类名。function Button({ isActive }) { return (<button className={isActive ? "btn btn-active" : "btn"}>Submit</button>); }可结合 classnames 库简化多个类名的处理。

内联样式示例:

function StyledHeader() {
  const headerStyle = {
    color: "blue",
    fontSize: "24px",
    textAlign: "center"
  };
  return <h1 style={headerStyle}>Styled Title</h1>;
}

className 示例:

/* App.css */
.title { font-weight: bold; color: green; }
// Component
import './App.css';
function StyledTitle() {
  return <h2 className="title">Hello World</h2>;
}

2.6 JSX 与 JavaScript 的转换原理

概念说明注意事项
Babel 编译JSX 在构建时通过 Babel 转换为 React.createElement() 调用。开发者无需手动调用,但理解转换有助于调试。
React.createElement()React 用于创建虚拟 DOM 节点的底层 API。接收标签名、属性对象、子元素等参数。参数顺序:type, props, ...children
转换示例<h1>Hello</h1>React.createElement('h1', null, 'Hello')深层嵌套的 JSX 会转换为嵌套的 createElement 调用。
方法语法用途代码示例注意事项
React.createElementReact.createElement(type, props, ...children)创建 React 元素(虚拟 DOM 节点)。见下方示例现代开发中极少直接使用,JSX 更简洁。但理解其原理有助于掌握 React 渲染机制。

React.createElement 示例:

import React from 'react';

function RawElement() {
  return React.createElement(
    'div',
    { className: 'container' },
    React.createElement('h1', null, 'Title'),
    React.createElement('p', null, 'Content')
  );
}

第三章:组件与 Props

3.1 函数组件与类组件的定义

名称语法用途注意事项
函数组件function ComponentName(props) { return <JSX />; }const ComponentName = (props) => { return <JSX />; };使用 JavaScript 函数定义 React 组件,简洁、易于测试,推荐现代 React 使用。必须首字母大写;必须返回 JSX 或 null;React 16.8+ 支持 Hooks。
类组件class ComponentName extends React.Component { render() { return <JSX />; } }使用 ES6 类定义组件,可使用生命周期方法和内部状态(state)。必须继承 React.Component;必须实现 render() 方法;this 需注意绑定。

代码示例:

// 函数组件示例
function WelcomeFunction({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// 类组件示例
import React from 'react';

class WelcomeClass extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

3.2 组件的复用与组合

名称语法用途注意事项
组件复用<Component /> 多次使用将 UI 拆分为独立组件,在不同位置重复使用,减少重复代码。组件应保持独立、可配置(通过 props)。
组件组合<Parent><Child /></Parent>通过嵌套组件构建复杂 UI,父组件包含子组件作为 children。利用 props.children 接收并渲染子元素,提升灵活性。

代码示例:

// 可复用的按钮组件
function Button({ label, onClick, variant = "primary" }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  );
}

// 组合使用多个按钮
function ActionPanel() {
  return (
    <div className="panel">
      <Button label="Save" onClick={() => alert('Saved!')} variant="success" />
      <Button label="Delete" onClick={() => alert('Deleted!')} variant="danger" />
    </div>
  );
}

3.3 Props 的传递与使用

名称语法用途注意事项
传递 Props<Component propName="value" /><Component propName={value} />从父组件向子组件传递数据或回调函数。静态值用引号,动态值用 {}
接收 Propsfunction Component(props) { ... }function Component({ name }) { ... }子组件接收父组件传入的属性。Props 是只读的,子组件不能修改。
展开传递 Props<Component {...obj} />将对象的所有属性批量传递给组件。避免传递不必要的属性,可能引发警告。

代码示例:

// 子组件:接收并使用 props
function UserProfile({ name, email, onLogout }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
      <button onClick={onLogout}>Logout</button>
    </div>
  );
}

// 父组件:传递 props
function App() {
  const user = { name: "Alice", email: "alice@example.com" };
  return (
    <UserProfile
      name={user.name}
      email={user.email}
      onLogout={() => alert('Logged out!')}
    />
  );
}

3.4 Props 的类型检查(PropTypes)

名称语法用途注意事项
PropTypesComponent.propTypes = { propName: PropTypes.string }在开发环境中验证传入 props 的类型,防止运行时错误。仅在开发模式生效;需安装 prop-types 包:npm install prop-types
常用类型PropTypes.stringPropTypes.numberPropTypes.boolPropTypes.arrayPropTypes.objectPropTypes.funcPropTypes.nodePropTypes.element定义 props 的期望类型。node 可包含任何可渲染内容(字符串、数字、元素等);element 仅限 React 元素。
必填验证PropTypes.string.isRequired确保某个 prop 必须被提供。若未传且非必填,默认值可能仍适用。

代码示例:

import PropTypes from 'prop-types';

function UserCard({ name, age, avatar, children }) {
  return (
    <div className="card">
      <img src={avatar} alt={name} width="50" />
      <h4>{name}</h4>
      <p>Age: {age}</p>
      {children}
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  avatar: PropTypes.string,
  children: PropTypes.node
};

UserCard.defaultProps = {
  avatar: "/default-avatar.png"
};

3.5 组件的默认 Props

名称语法用途注意事项
defaultPropsComponent.defaultProps = { propName: value }为组件的 props 定义默认值,当父组件未传递时使用。默认值在类型检查之前解析,确保 isRequired 不会因默认值而失效。

代码示例:

function Greeting({ name, greeting }) {
  return <h2>{greeting}, {name}!</h2>;
}

Greeting.defaultProps = {
  greeting: "Hello",
  name: "Guest"
};

// 使用时可省略 greeting
function App() {
  return (
    <>
      <Greeting name="Alice" />
      <Greeting name="Bob" greeting="Hi" />
    </>
  );
}

3.6 组件的可组合性与单一职责原则

名称语法用途注意事项
props.children{children}接收并渲染组件标签内的子元素,实现内容插槽。是 React 组合的核心机制,提升组件灵活性。
单一职责每个组件只负责一个功能或 UI 区块保持组件小而专注,易于测试、复用和维护。避免”巨型组件”;可拆分为 Header、Sidebar、ModalBody 等。
容器与展示分离容器组件管理逻辑和状态,展示组件仅负责渲染提高展示组件的可复用性和可测试性。展示组件应为纯函数,仅依赖 props。

代码示例:

// 展示组件:仅负责渲染
function Card({ title, children, footer }) {
  return (
    <div className="card">
      <div className="card-header">
        <h3>{title}</h3>
      </div>
      <div className="card-body">
        {children}
      </div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// 容器组件:组合并传递数据
function UserProfileCard({ user }) {
  return (
    <Card
      title={user.name}
      footer={<button>Edit</button>}
    >
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>
    </Card>
  );
}

第四章:State 与事件处理

4.1 类组件中的 state 状态管理

名称语法用途注意事项
statethis.state = { key: value }类组件中用于存储可变数据的对象,状态变化会触发组件重新渲染。state 必须为纯对象;初始化在构造函数或类字段中进行。
类字段语法(推荐)state = { count: 0 }在类中直接定义 state,无需构造函数,语法更简洁。需 Babel 支持类属性;现代 React 项目普遍使用。

代码示例:

import React from 'react';

class Counter extends React.Component {
  // 使用类字段语法初始化 state
  state = {
    count: 0
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
      </div>
    );
  }
}

4.2 状态的初始化与更新(setState)

名称语法用途注意事项
setState(对象形式)this.setState({ key: value })更新状态的一部分,React 合并新值到现有 state适用于无依赖的简单更新;不能在更新后立即读取 this.state 获取新值。
setState(函数形式)this.setState((prevState, props) => ({ count: prevState.count + 1 }))接收函数,参数为前一个状态和当前 props,返回新状态对象。用于依赖前一个状态的更新(如递增),避免异步更新导致的竞态。

代码示例:

import React from 'react';

class Counter extends React.Component {
  state = { count: 0 };

  increment = () => {
    // 使用函数形式确保基于最新状态更新
    this.setState((prevState) => ({ count: prevState.count + 1 }));
  };

  reset = () => {
    // 使用对象形式重置状态
    this.setState({ count: 0 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>+1</button>
        <button onClick={this.reset}>Reset</button>
      </div>
    );
  }
}

4.3 事件处理:绑定与调用

名称语法用途注意事项
方法绑定(构造函数)this.handleClick = this.handleClick.bind(this)在构造函数中绑定 this,确保事件处理函数中 this 指向组件实例。冗长,但兼容性好。
类字段 + 箭头函数(推荐)handleClick = () => { ... }使用箭头函数自动绑定 this,语法简洁,无需手动绑定。现代 React 项目推荐方式。
内联箭头函数<button onClick={() => this.handleClick()}>在 JSX 中直接使用箭头函数调用处理函数。每次渲染生成新函数,可能影响性能(尤其在列表中)。

代码示例:

import React from 'react';

class ToggleButton extends React.Component {
  state = { isOn: false };

  // 推荐:使用类字段 + 箭头函数自动绑定 this
  handleClick = () => {
    this.setState((prevState) => ({ isOn: !prevState.isOn }));
  };

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

4.4 向事件处理函数传递参数

名称语法用途注意事项
内联箭头函数<button onClick={() => this.handleDelete(id)}>Delete</button>在事件触发时传递额外参数(如 ID)。每次渲染创建新函数,可能影响性能。
bind 方法<button onClick={this.handleEdit.bind(this, id)}>Edit</button>使用 bind 预设参数,this 作为第一个参数。bind 也会创建新函数,但语法略显冗长。

代码示例:

import React from 'react';

class UserList extends React.Component {
  users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  handleDelete = (id) => {
    console.log('Deleting user:', id);
    // 实际项目中调用 API 或更新状态
  };

  handleEdit = (id) => {
    console.log('Editing user:', id);
  };

  render() {
    return (
      <ul>
        {this.users.map(user => (
          <li key={user.id}>
            {user.name}
            <button onClick={() => this.handleDelete(user.id)}>Delete</button>
            <button onClick={this.handleEdit.bind(this, user.id)}>Edit</button>
          </li>
        ))}
      </ul>
    );
  }
}

4.5 状态更新的异步性与批处理

名称说明注意事项
异步更新this.setState 是异步的,不会立即改变 this.state不能在 setState 后立即读取 this.state 判断新值。
批处理(Batching)React 将多个 setState 调用合并为一次更新,提升性能。在事件处理函数中自动批处理;在异步代码(如 setTimeoutPromise)中可能不批处理(React 18+ 已优化)。
回调函数(旧式)this.setState(newState, callback)在状态更新并重新渲染后执行回调。

代码示例:

import React from 'react';

class AsyncCounter extends React.Component {
  state = { count: 0 };

  // 错误示范:依赖异步更新
  badIncrement = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 可能仍为旧值
  };

  // 正确示范:使用函数形式
  goodIncrement = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };

  // 多次调用会被批处理
  tripleIncrement = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    // 最终 count 只增加 1(批处理合并)
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.goodIncrement}>+1</button>
        <button onClick={this.tripleIncrement}>+3 (batched)</button>
      </div>
    );
  }
}

4.6 函数组件中使用 useState Hook(简介)

名称语法用途注意事项
useStateconst [state, setState] = useState(initialValue)Hook,用于在函数组件中添加状态。返回状态值和更新函数。只能在函数组件顶层调用;不能在条件或循环中使用。
初始值useState(0)useState(() => expensiveCalc())设置初始状态。函数形式用于惰性初始化。惰性初始化适用于昂贵计算,仅在首次渲染执行。
状态更新setState(newValue)setState(prev => newValue)更新状态,触发重新渲染。函数形式用于依赖前一个状态的更新。

代码示例:

import React, { useState } from 'react';

function Counter() {
  // 使用 useState 添加状态
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(prev => prev + 1); // 推荐使用函数形式
  };

  const reset = () => {
    setCount(0);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default Counter;

第五章:列表渲染与 Keys

5.1 使用 map 渲染列表

名称语法用途注意事项
Array.prototype.map()array.map((item, index) => <Component key={index} />)遍历数组,为每个元素生成 JSX 元素,用于动态渲染列表。必须返回 JSX 或 null;不修改原数组;需为每个元素提供唯一 key。
条件渲染结合 maparray.map(item => condition ? <Item /> : null)在映射过程中根据条件决定是否渲染某项。避免使用 filter 预处理更清晰,除非逻辑简单。

代码示例:

import React from 'react';

function TodoList() {
  const todos = ['Learn React', 'Build a Project', 'Deploy App'];

  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>{todo}</li>
      ))}
    </ul>
  );
}

export default TodoList;

5.2 Keys 的作用与选择原则

名称说明注意事项
Key 的作用React 使用 key 来识别列表中每个元素的唯一性,帮助 React 确定哪些元素被添加、删除或重新排序,从而高效更新 DOM。key 是同级元素之间的标识,不传递给组件的 props。
选择原则:使用稳定唯一 ID优先使用数据中的唯一标识符,如 iduserIdproductId推荐方案,确保 key 稳定、可预测、唯一。
备选方案:使用索引(index)当数据无稳定 ID 且列表静态、无排序、无增删时,可使用数组索引。❌ 不推荐用于动态列表,会导致性能问题和状态错乱。
避免使用随机数Math.random()Date.now()每次渲染 key 都变化,导致 React 重新创建所有 DOM,失去优化意义。

代码示例:

import React from 'react';

function UserList() {
  const users = [
    { id: 101, name: 'Alice' },
    { id: 102, name: 'Bob' },
    { id: 103, name: 'Charlie' }
  ];

  return (
    <ul>
      {users.map(user => (
        // ✅ 正确:使用稳定唯一的 id 作为 key
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

5.3 Keys 的使用场景与注意事项

场景/注意事项说明
必须在 map 中提供 keyReact 会警告:Each child in a list should have a unique "key" prop.
Key 必须在兄弟节点中唯一只需在同一 map 调用的兄弟元素中唯一,不同列表的 key 可重复。
Key 不会传递给组件子组件无法通过 props.key 访问 key。如需 ID,应显式传递:<User key={id} id={id} />
不要在子组件内部使用 index 作为 key即使子组件封装了列表,key 也应在 map 外部指定。
Fragment 也需要 key当 map 返回多个元素(如 <React.Fragment>)时,每个 Fragment 需要 key。

代码示例:

import React from 'react';

function MessageList() {
  const messages = [
    { id: 1, text: 'Hello', sender: 'Alice' },
    { id: 2, text: 'Hi', sender: 'Bob' }
  ];

  return (
    <div>
      {messages.map(message => (
        // ✅ 正确:为每个 Fragment 提供 key
        <React.Fragment key={message.id}>
          <strong>{message.sender}:</strong>
          <span> {message.text}</span>
          <br />
        </React.Fragment>
      ))}
    </div>
  );
}

// 子组件接收显式传递的 id
function UserItem({ user }) {
  return <li>{user.name}</li>;
}

function App() {
  const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
  return (
    <ul>
      {users.map(user => (
        // ✅ 正确:key 在 map 中指定,id 显式传递
        <UserItem key={user.id} user={user} />
      ))}
    </ul>
  );
}

5.4 列表项中状态管理的常见问题

问题描述解决方案注意事项
使用索引作为 key 导致状态错乱当列表可排序、过滤或增删时,使用 index 作为 key 会导致 React 错误复用 DOM,组件状态(如输入框内容)错位。✅ 使用数据中的唯一 ID 作为 key。这是最常见的列表渲染 bug。
在列表项中使用 useState 导致状态与索引绑定如果状态基于索引存储(如 useState 在循环内),删除项后状态会整体前移,导致错乱。✅ 使用唯一 ID 管理状态(如对象映射 state[id]),或将状态提升到父组件。
未正确清理副作用列表项中使用 useEffect(函数组件)时,未返回清理函数,可能导致内存泄漏。✅ 在 useEffect 中返回清理函数。

代码示例:错误示范 vs 正确示范

import React, { useState } from 'react';

// ❌ 错误示范:使用索引作为 key,状态与索引绑定
function BadList() {
  const [items, setItems] = useState(['A', 'B', 'C']);
  const [edits, setEdits] = useState({}); // 用对象存储编辑状态

  const removeItem = (index) => {
    const newItems = items.filter((_, i) => i !== index);
    setItems(newItems);
    // ❌ 问题:删除后,后续项的索引变化,但 edits 状态仍按原索引存储,导致错乱
  };

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}> {/* ❌ 使用 index 作为 key */}
          <input
            value={edits[index] || ''}
            onChange={(e) => setEdits(prev => ({ ...prev, [index]: e.target.value }))}
          />
          <button onClick={() => removeItem(index)}>Remove</button>
        </li>
      ))}
    </ul>
  );
}

// ✅ 正确示范:使用唯一 ID,状态与 ID 绑定
function GoodList() {
  const [items, setItems] = useState([
    { id: 1, value: 'A' },
    { id: 2, value: 'B' },
    { id: 3, value: 'C' }
  ]);
  const [edits, setEdits] = useState({}); // 状态基于 id

  const removeItem = (id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}> {/* ✅ 使用唯一 id 作为 key */}
          <input
            value={edits[item.id] || ''}
            onChange={(e) => setEdits(prev => ({ ...prev, [item.id]: e.target.value }))}
          />
          <button onClick={() => removeItem(item.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
}

export default GoodList;

第六章:表单处理

6.1 受控组件(Controlled Components)

名称语法用途注意事项
受控组件<input value={state} onChange={handleChange} />表单元素的值由 React 状态(state)控制,通过 onChange 同步更新状态。值必须由 state 提供,不能为 undefinednull(除非是初始值)。
状态驱动使用 useState 管理表单数据将表单数据集中管理,便于验证、提交和重置。数据始终与 UI 同步,符合 React 声明式理念。

代码示例:

import React, { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  const handleChange = (e) => {
    setName(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitted name: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default NameForm;

6.2 处理文本输入、单选框、复选框、下拉框

输入类型语法用途注意事项
文本输入<input type="text" value={value} onChange={handleChange} />捕获用户输入的文本。value 必须绑定到状态。
单选框(Radio)<input type="radio" checked={checked} onChange={handleChange} />从多个选项中选择一项。使用 checked 而非 value 控制选中状态。
复选框(Checkbox)<input type="checkbox" checked={checked} onChange={handleChange} />切换布尔值(是/否)。e.target.checked 为布尔值,e.target.value 为字符串。
下拉框(Select)<select value={selected} onChange={handleChange}>从下拉列表中选择一项或多选。单选时 value 为字符串;多选时为数组。

代码示例:

import React, { useState } from 'react';

function UserProfileForm() {
  const [formData, setFormData] = useState({
    username: '',
    gender: 'male',
    subscribed: false,
    country: 'CN'
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form Data:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </label>

      <label>
        Gender:
        <label><input type="radio" name="gender" value="male" checked={formData.gender === 'male'} onChange={handleChange} /> Male</label>
        <label><input type="radio" name="gender" value="female" checked={formData.gender === 'female'} onChange={handleChange} /> Female</label>
      </label>

      <label>
        <input
          type="checkbox"
          name="subscribed"
          checked={formData.subscribed}
          onChange={handleChange}
        />
        Subscribe to newsletter
      </label>

      <label>
        Country:
        <select name="country" value={formData.country} onChange={handleChange}>
          <option value="US">United States</option>
          <option value="CN">China</option>
          <option value="DE">Germany</option>
        </select>
      </label>

      <button type="submit">Save</button>
    </form>
  );
}

export default UserProfileForm;

6.3 多个输入的统一处理

名称语法用途注意事项
统一事件处理器handleChange = (e) => { const { name, value } = e.target; setState({ [name]: value }); }使用一个函数处理多个输入,通过 name 属性区分字段。所有输入必须有 name 属性;适用于简单表单。
解构赋值const { name, value, type, checked } = e.target简化事件对象属性访问。处理复选框时注意 checkedvalue 的区别。

代码示例:

import React, { useState } from 'react';

function ContactForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    phone: '',
    message: ''
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Message from ${form.name}: ${form.message}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleInputChange} placeholder="Your Name" />
      <input name="email" value={form.email} onChange={handleInputChange} placeholder="Email" type="email" />
      <input name="phone" value={form.phone} onChange={handleInputChange} placeholder="Phone" type="tel" />
      <textarea name="message" value={form.message} onChange={handleInputChange} placeholder="Message" />
      <button type="submit">Send</button>
    </form>
  );
}

export default ContactForm;

6.4 非受控组件与 Ref 的使用(简介)

名称语法用途注意事项
useRef Hookconst inputRef = useRef();创建对 DOM 元素的引用,用于直接访问表单值。不推荐用于常规表单,仅在特定场景(如文件输入、聚焦、第三方库集成)使用。
非受控组件<input defaultValue="initial" ref={inputRef} />值不由 React 状态控制,通过 ref 在提交时读取。使用 defaultValue 而非 value;避免与受控组件混合。

代码示例:

import React, { useRef } from 'react';

function FileUploadForm() {
  const fileInputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const file = fileInputRef.current.files[0];
    if (file) {
      alert(`Selected file: ${file.name} (${file.size} bytes)`);
    } else {
      alert('No file selected');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Upload File:
        <input type="file" ref={fileInputRef} />
      </label>
      <button type="submit">Upload</button>
    </form>
  );
}

export default FileUploadForm;

6.5 表单验证基础

名称语法用途注意事项
内联验证(HTML5)<input required minLength="2" pattern="[a-zA-Z]+" />使用浏览器内置验证。简单有效,但样式和提示不可定制。
手动验证onChangeonSubmit 中检查值,设置错误状态。实现自定义验证逻辑和错误提示。验证状态应与表单状态分离管理。
错误状态管理const [errors, setErrors] = useState({})存储每个字段的验证错误信息。提交时集中验证,输入时可做即时反馈。

代码示例:

import React, { useState } from 'react';

function ValidatedForm() {
  const [form, setForm] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!form.email) newErrors.email = 'Email is required';
    else if (!/\S+@\S+\.\S+/.test(form.email)) newErrors.email = 'Email is invalid';

    if (!form.password) newErrors.password = 'Password is required';
    else if (form.password.length < 6) newErrors.password = 'Password must be at least 6 characters';

    return newErrors;
  };

  const handleChange = (e) => {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
    // 可选:输入时清除对应错误
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: undefined }));
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = validate();
    if (Object.keys(newErrors).length === 0) {
      alert('Form submitted successfully!');
    } else {
      setErrors(newErrors);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Email:
        <input
          type="email"
          name="email"
          value={form.email}
          onChange={handleChange}
        />
        {errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
      </label>

      <label>
        Password:
        <input
          type="password"
          name="password"
          value={form.password}
          onChange={handleChange}
        />
        {errors.password && <span style={{ color: 'red' }}>{errors.password}</span>}
      </label>

      <button type="submit">Login</button>
    </form>
  );
}

export default ValidatedForm;

第七章:条件渲染

7.1 if 语句与条件逻辑

名称语法用途注意事项
if 语句if (condition) { return <JSX />; }在组件函数内部使用 if 判断,决定返回哪个 JSX。适用于复杂逻辑或多分支条件;必须在函数作用域内使用。
早期返回if (!prop) return null;在渲染前检查必要条件,提前返回 null 或占位内容。提升代码可读性,避免深层嵌套。

代码示例:

import React from 'react';

function Greeting({ user }) {
  if (user) {
    return <h1>Welcome back, {user.name}!</h1>;
  } else {
    return <h1>Please sign in.</h1>;
  }
}

function Dashboard({ isLoggedIn }) {
  // 早期返回
  if (!isLoggedIn) {
    return <div>Please log in to view dashboard.</div>;
  }

  return (
    <div>
      <h2>Dashboard</h2>
      <p>Your data is secure.</p>
    </div>
  );
}

export default Dashboard;

7.2 与运算符 &&

名称语法用途注意事项
逻辑与(&&){condition && <Component />}condition 为真时渲染右侧的 JSX,否则不渲染(返回 null)。简洁,适用于”存在即渲染”的场景;左侧不能为 0falsenull 等,否则会渲染出来。

代码示例:

import React, { useState } from 'react';

function Notification() {
  const [hasNewMessage, setHasNewMessage] = useState(true);

  return (
    <div>
      <h3>Inbox</h3>
      {/* 只有当有新消息时才显示提示 */}
      {hasNewMessage && <p style={{ color: 'blue' }}>You have a new message!</p>}
      <button onClick={() => setHasNewMessage(false)}>Mark as Read</button>
    </div>
  );
}

export default Notification;

7.3 三元运算符

名称语法用途注意事项
三元运算符{condition ? <A /> : <B />}根据条件真假选择渲染两个组件中的一个。适用于二选一渲染;比 if 更简洁,适合 JSX 内联使用。
嵌套三元{cond1 ? A : cond2 ? B : C}多条件判断。❌ 不推荐,可读性差,应使用 ifswitch 替代。

代码示例:

import React, { useState } from 'react';

function ToggleContent() {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    <div>
      {/* 根据加载状态显示不同内容 */}
      {isLoaded ? (
        <div>
          <h2>Data Loaded</h2>
          <p>Your content is ready.</p>
        </div>
      ) : (
        <p>Loading...</p>
      )}

      <button onClick={() => setIsLoaded(!isLoaded)}>
        {isLoaded ? 'Reset' : 'Load Data'}
      </button>
    </div>
  );
}

export default ToggleContent;

7.4 阻止组件渲染(返回 null)

名称语法用途注意事项
返回 nullreturn null;render 或函数组件中返回 null,阻止该组件在 DOM 中渲染任何内容。合法且常用;React 不会渲染 null,也不会产生副作用。
条件性包装组件组件根据 props 决定是否渲染子元素。实现权限控制、加载状态等。父组件可控制子组件的可见性。

代码示例:

import React from 'react';

// 阻止自身渲染
function AdminPanel({ isAdmin }) {
  if (!isAdmin) {
    // 不渲染任何内容
    return null;
  }

  return (
    <div className="admin-panel">
      <h3>Admin Controls</h3>
      <button>Delete User</button>
    </div>
  );
}

// 条件性包装组件
function Visible({ when, children }) {
  return when ? children : null;
}

function App() {
  const user = { isAdmin: false };

  return (
    <div>
      <h1>My App</h1>
      {/* 使用包装组件 */}
      <Visible when={user.isAdmin}>
        <AdminPanel />
      </Visible>
    </div>
  );
}

export default App;

7.5 条件渲染的最佳实践

实践说明示例
优先使用 && 和三元在 JSX 内部,优先使用 &&? : 实现简单条件渲染,保持模板清晰。{isLoggedIn && <Dashboard />}
复杂逻辑提取到变量或函数避免在 JSX 中写复杂判断,提前计算好要渲染的内容。const content = getUserContent(); return <div>{content}</div>;
避免在条件中渲染 0 或空字符串0 && <div /> 会渲染 0 而非组件,因 0 为假但会被显示。使用 !!value 或明确比较。
使用组件拆分将不同状态的 UI 拆分为独立组件,提高可复用性。<Loading /><Error /><Success />

代码示例:

import React, { useState } from 'react';

// ✅ 最佳实践:将复杂条件逻辑提取
function UserData({ status, data }) {
  // 提前计算内容,避免 JSX 中复杂逻辑
  const getContent = () => {
    switch (status) {
      case 'loading':
        return <p>Loading user data...</p>;
      case 'error':
        return <p style={{ color: 'red' }}>Failed to load data.</p>;
      case 'success':
        return (
          <div>
            <h3>{data.name}</h3>
            <p>Email: {data.email}</p>
          </div>
        );
      default:
        return null;
    }
  };

  const content = getContent();

  return (
    <div>
      <h2>User Profile</h2>
      {/* JSX 内部简洁明了 */}
      {content}
    </div>
  );
}

function App() {
  const [user, setUser] = useState({ status: 'loading' });

  const loadUser = () => {
    setUser({
      status: 'success',
      data: { name: 'Alice', email: 'alice@example.com' }
    });
  };

  return (
    <UserData status={user.status} data={user.data} />
  );
}

export default App;

第八章:组件的生命周期(类组件)

8.1 挂载阶段(Mounting)生命周期方法

名称语法用途注意事项
constructorconstructor(props) { super(props); this.state = {}; }初始化 state 和绑定事件处理函数。必须调用 super(props);不能调用 setState
static getDerivedStateFromPropsstatic getDerivedStateFromProps(props, state)将 props 同步到 state(罕见需求)。静态方法,无 this;返回 null 或状态对象;避免副作用。
renderrender() { return <JSX />; }必须实现,返回要渲染的 JSX。纯函数,不能修改 state 或调用 setState
componentDidMountcomponentDidMount() { ... }组件挂载后执行,适合发起网络请求、设置定时器、操作 DOM。可安全调用 setState,触发更新。

代码示例:

import React from 'react';

class UserProfile extends React.Component {
  state = { user: null, loading: true };

  componentDidMount() {
    // 模拟 API 请求
    setTimeout(() => {
      this.setState({
        user: { id: 1, name: 'Alice', email: 'alice@example.com' },
        loading: false
      });
    }, 1000);
  }

  render() {
    const { loading, user } = this.state;
    if (loading) return <p>Loading...</p>;
    return (
      <div>
        <h2>{user.name}</h2>
        <p>{user.email}</p>
      </div>
    );
  }
}

export default UserProfile;

8.2 更新阶段(Updating)生命周期方法

名称语法用途注意事项
static getDerivedStateFromPropsstatic getDerivedStateFromProps(props, state)在 props 或 state 更新时调用,用于同步数据。与挂载阶段相同;避免在此执行副作用。
shouldComponentUpdateshouldComponentUpdate(nextProps, nextState)性能优化,控制组件是否重新渲染。返回 false 可跳过渲染。返回 false 时,render 和后续生命周期不执行;默认返回 true
renderrender() { ... }重新渲染组件。同挂载阶段。
getSnapshotBeforeUpdategetSnapshotBeforeUpdate(prevProps, prevState)在 DOM 更新前获取快照(如滚动位置),返回值传给 componentDidUpdate不常用;用于保存更新前的 DOM 状态。
componentDidUpdatecomponentDidUpdate(prevProps, prevState, snapshot)组件更新后执行,适合根据 props 变化发起请求或操作 DOM。可调用 setState,但必须在条件判断中,否则导致无限循环。

代码示例:

import React from 'react';

class ChatList extends React.Component {
  state = { messages: [] };

  componentDidUpdate(prevProps) {
    // 当 roomId 变化时,加载新房间的消息
    if (prevProps.roomId !== this.props.roomId) {
      this.loadMessages(this.props.roomId);
    }
  }

  loadMessages = (roomId) => {
    // 模拟加载消息
    this.setState({ messages: [`Message 1 in ${roomId}`, `Message 2 in ${roomId}`] });
  };

  render() {
    return (
      <div>
        <h3>Room: {this.props.roomId}</h3>
        <ul>
          {this.state.messages.map((msg, i) => (
            <li key={i}>{msg}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default ChatList;

8.3 卸载阶段(Unmounting)生命周期方法

名称语法用途注意事项
componentWillUnmountcomponentWillUnmount() { ... }组件卸载前执行,用于清理资源:清除定时器、取消网络请求、移除事件监听器。不能调用 setState,因为组件即将被销毁。

代码示例:

import React from 'react';

class Timer extends React.Component {
  state = { seconds: 0 };

  componentDidMount() {
    // 设置定时器
    this.interval = setInterval(() => {
      this.setState(prev => ({ seconds: prev.seconds + 1 }));
    }, 1000);
  }

  componentWillUnmount() {
    // 清理定时器,防止内存泄漏
    clearInterval(this.interval);
  }

  render() {
    return <p>Timer: {this.state.seconds}s</p>;
  }
}

export default Timer;

8.4 错误处理(Error Boundaries)

名称语法用途注意事项
static getDerivedStateFromErrorstatic getDerivedStateFromError(error)在子组件抛出错误后调用,返回新的 state 以显示降级 UI。静态方法,用于更新状态;不能访问 this
componentDidCatchcomponentDidCatch(error, info)捕获错误信息和组件栈,适合记录错误日志。可用于上报错误到监控服务。

代码示例:

import React from 'react';

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    // 更新 state,让下一次渲染显示降级 UI
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    // 例如:发送错误日志到服务器
    console.error('Error caught by boundary:', error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ color: 'red' }}>
          <h2>Something went wrong.</h2>
          <details>{this.state.error?.toString()}</details>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用示例
function BrokenComponent() {
  throw new Error('I crashed!');
}

function App() {
  return (
    <ErrorBoundary>
      <BrokenComponent />
    </ErrorBoundary>
  );
}

export default App;

8.5 生命周期方法的执行顺序与使用场景

阶段方法调用顺序典型使用场景
挂载(Mounting)constructorgetDerivedStateFromPropsrendercomponentDidMount初始化状态、发起首次数据请求、设置监听器。
更新(Updating)getDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate响应 props 变化、性能优化、更新后操作 DOM。
卸载(Unmounting)componentWillUnmount清理定时器、取消订阅、移除事件监听。
错误处理getDerivedStateFromErrorcomponentDidCatchrender捕获渲染错误,显示友好错误界面。

完整生命周期示例:

import React from 'react';

class LifecycleDemo extends React.Component {
  state = { count: 0 };

  constructor(props) {
    super(props);
    console.log('1. constructor');
  }

  static getDerivedStateFromProps(props, state) {
    console.log('2. getDerivedStateFromProps');
    return null;
  }

  componentDidMount() {
    console.log('4. componentDidMount');
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('5. shouldComponentUpdate');
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('6. getSnapshotBeforeUpdate');
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('7. componentDidUpdate');
  }

  componentWillUnmount() {
    console.log('8. componentWillUnmount');
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log('3. render');
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

export default LifecycleDemo;

第九章:useState Hook

9.1 useState 基本语法与使用

名称语法用途注意事项
useStateconst [state, setState] = useState(initialValue);声明一个状态变量及其更新函数。只能在函数组件顶层调用,不能在条件或循环中使用。
状态读取{state}在 JSX 中使用当前状态值。状态是不可变的,每次更新都会生成新值。
状态更新setState(newValue)更新状态,触发组件重新渲染。新值会替换旧值(非合并),函数组件无 this.state

代码示例:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

export default Counter;

9.2 状态的初始化(函数形式)

名称语法用途注意事项
函数初始化useState(() => expensiveCalculation())延迟执行昂贵的计算,仅在组件首次渲染时运行。避免每次渲染都执行高成本操作;传入函数,不是调用函数。

代码示例:

import React, { useState } from 'react';

function UserProfile({ userId }) {
  // ✅ 正确:函数形式,只在首次渲染时计算
  const [user, setUser] = useState(() => {
    console.log('Fetching user data...');
    return { id: userId, name: 'Alice', profileLoaded: false };
  });

  return (
    <div>
      <h2>Welcome, {user.name}</h2>
      <p>ID: {user.id}</p>
    </div>
  );
}

export default UserProfile;

9.3 函数式更新状态

名称语法用途注意事项
函数式更新setState(prevState => prevState + 1)使用前一个状态值计算新状态,确保更新基于最新值。当新状态依赖于旧状态时必须使用,避免闭包问题。

代码示例:

import React, { useState } from 'react';

function BatchCounter() {
  const [count, setCount] = useState(0);

  // ❌ 错误:基于闭包中的旧 count 值,点击一次只会加 1
  const badIncrement = () => {
    setCount(count + 1);
    setCount(count + 1); // 仍然基于旧值
  };

  // ✅ 正确:函数式更新,每次基于最新状态
  const goodIncrement = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={badIncrement}>Bad +2</button> {/* 实际只 +1 */}
      <button onClick={goodIncrement}>Good +2</button> {/* 正确 +2 */}
    </div>
  );
}

export default BatchCounter;

9.4 处理对象与数组状态

数据类型语法用途注意事项
对象状态setState({ ...prevState, key: value })更新对象状态的特定字段。不能直接修改原对象;必须使用展开运算符创建新对象。
数组状态setState([...prevState, newItem])添加、删除或修改数组元素。避免使用 pushpopsplice 等变异方法;使用 filtermapconcat 等纯函数。

代码示例:

import React, { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', completed: false }
  ]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (input.trim()) {
      const newTodo = {
        id: Date.now(),
        text: input,
        completed: false
      };
      // ✅ 使用展开运算符创建新数组
      setTodos(prev => [...prev, newTodo]);
      setInput('');
    }
  };

  const toggleTodo = (id) => {
    // ✅ 使用 map 创建新数组,不修改原数组
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const removeTodo = (id) => {
    // ✅ 使用 filter 创建新数组
    setTodos(prev => prev.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        placeholder="Add a todo"
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;

9.5 常见陷阱与最佳实践

陷阱/实践说明解决方案
直接修改状态state.push()state.name = 'new'❌ 会导致 React 无法检测到变化,UI 不更新。✅ 始终返回新对象/数组。
忽略函数式更新在事件处理中多次 setState 依赖旧状态。✅ 使用 (prev) => prev + 1 形式。
状态合并问题useState 不像 this.setState 会自动合并对象。✅ 手动使用 {...prevState, ...newValues} 合并。
多个状态的拆分将不相关的状态合并到一个对象中。✅ 拆分为多个 useState 调用,提高可维护性。
状态提升多个组件需要共享状态时。✅ 将状态提升到最近的共同父组件。

代码示例:最佳实践 - 拆分状态

import React, { useState } from 'react';

// ✅ 好:拆分不相关的状态
function UserForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(18);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    // 模拟提交
    await new Promise(resolve => setTimeout(resolve, 1000));
    alert(`Submitted: ${name}, ${email}, ${age}`);
    setIsSubmitting(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={e => setName(e.target.value)} placeholder="Name" required />
      <input value={email} onChange={e => setEmail(e.target.value)} placeholder="Email" type="email" required />
      <input value={age} onChange={e => setAge(Number(e.target.value))} type="number" />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

export default UserForm;

第十章:useEffect Hook

10.1 useEffect 基本语法

名称语法用途注意事项
useEffectuseEffect(() => { /* effect */ }, [dependencies]);执行副作用(如数据获取、订阅、手动 DOM 操作)。必须在函数组件顶层调用;回调函数不能是 async
副作用函数() => { ... }包含要执行的副作用逻辑。可返回一个清理函数。
依赖数组[dep1, dep2]控制 effect 的执行时机。依赖项必须包含所有在 effect 中使用的响应式值(props、state)。

代码示例:

import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  // 每秒更新一次
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // 清理函数
    return () => clearInterval(interval);
  }, []); // 依赖数组为空,只在挂载时执行

  return <p>Timer: {seconds}s</p>;
}

export default Timer;

10.2 模拟 componentDidMount

名称语法用途注意事项
空依赖数组useEffect(() => { ... }, [])在组件首次渲染后执行一次,等效于 componentDidMount常用于初始化数据获取、设置订阅、记录分析等。
初始化副作用在 effect 中发起 API 请求或设置监听。避免在组件体中直接执行副作用。effect 在渲染后异步执行。

代码示例:

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  // ✅ 模拟 componentDidMount:只在首次渲染后执行
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // 依赖数组为空

  return (
    <div>
      {user ? <h2>{user.name}</h2> : <p>Loading...</p>}
    </div>
  );
}

export default UserProfile;

10.3 模拟 componentDidUpdate

名称语法用途注意事项
非空依赖数组useEffect(() => { ... }, [prop, state])当依赖项变化时执行,等效于 componentDidUpdate避免遗漏依赖,否则可能使用过时值。
响应 props/state 变化根据 props 或 state 的变化重新获取数据或更新 DOM。不要用于所有更新,仅当依赖特定值时。可能导致频繁执行,注意性能。

代码示例:

import React, { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  // ✅ 当 roomId 变化时,重新加载消息
  useEffect(() => {
    if (!roomId) return;
    fetch(`/api/rooms/${roomId}/messages`)
      .then(res => res.json())
      .then(data => setMessages(data));
  }, [roomId]); // 依赖 roomId

  return (
    <div>
      <h3>Room: {roomId}</h3>
      <ul>
        {messages.map((msg, i) => <li key={i}>{msg}</li>)}
      </ul>
    </div>
  );
}

export default ChatRoom;

10.4 模拟 componentWillUnmount

名称语法用途注意事项
返回清理函数useEffect(() => { return () => { /* cleanup */ }; }, [])在组件卸载前执行清理逻辑,等效于 componentWillUnmount必须在 effect 内部返回函数。
清理资源清除定时器、取消网络请求、移除事件监听器。防止内存泄漏和无效状态更新。清理函数在每次 effect 重新执行前也会调用。

代码示例:

import React, { useState, useEffect } from 'react';

function MouseTracker() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

    // 添加事件监听
    window.addEventListener('mousemove', handleMove);

    // ✅ 清理函数:移除事件监听
    return () => {
      window.removeEventListener('mousemove', handleMove);
    };
  }, []); // 只在挂载/卸载时执行

  return (
    <p>Mouse at: {position.x}, {position.y}</p>
  );
}

export default MouseTracker;

10.5 依赖数组的作用与优化

依赖类型语法用途注意事项
空数组 []useEffect(() => {}, [])只在挂载时执行一次。确保 effect 内不引用任何 props/state,否则会捕获初始值。
有依赖 [a, b]useEffect(() => {}, [a, b])ab 变化时重新执行。必须包含所有使用的响应式值。
无依赖(省略)useEffect(() => {})每次渲染后都执行。很少使用,可能导致性能问题。

代码示例:

import React, { useState, useEffect } from 'react';

function Example({ userId, theme }) {
  const [data, setData] = useState(null);

  // 仅当 userId 变化时重新获取数据
  useEffect(() => {
    fetch(`/api/users/${userId}/profile`)
      .then(res => res.json())
      .then(setData);
  }, [userId]); // ✅ 正确依赖

  // 仅当 theme 变化时更新文档标题
  useEffect(() => {
    document.title = `App - ${theme}`;
  }, [theme]); // ✅ 正确依赖

  return <div>{data ? data.name : 'Loading...'}</div>;
}

export default Example;

10.6 清理副作用(返回清理函数)

名称语法用途注意事项
清理函数return () => { /* cleanup logic */ };清理上一次 effect 创建的资源。每次 effect 执行前都会先调用上一次的清理函数。
避免内存泄漏确保定时器、订阅、监听器被正确移除。尤其在频繁重新渲染的组件中至关重要。清理函数在组件卸载时也会执行。

代码示例:

import React, { useState, useEffect } from 'react';

function DataPoller({ url }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let cancelled = false; // 标记是否已取消

    const poll = async () => {
      while (!cancelled) {
        const res = await fetch(url);
        const json = await res.json();
        if (!cancelled) {
          setData(json);
        }
        await new Promise(resolve => setTimeout(resolve, 5000)); // 5s 轮询
      }
    };

    poll();

    // ✅ 清理函数:设置 cancelled 为 true,停止轮询
    return () => {
      cancelled = true;
    };
  }, [url]);

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

export default DataPoller;

10.7 常见错误:无限循环、遗漏依赖

错误类型说明解决方案
无限循环useEffect 中更新了依赖数组中的状态,且无终止条件。✅ 添加条件判断;使用函数式更新;检查依赖是否必要。
遗漏依赖effect 使用了 props/state 但未加入依赖数组,导致闭包问题。✅ 使用 ESLint 插件 eslint-plugin-react-hooks 自动检测;或使用 useCallback/useMemo 包装。
错误地使用 async直接将 effect 回调设为 async✅ 在 effect 内部定义 async 函数并调用。

代码示例:修复常见错误

import React, { useState, useEffect } from 'react';

function CorrectedExample({ userId }) {
  const [user, setUser] = useState(null);

  // ✅ 正确:在 effect 内部使用 async 函数
  useEffect(() => {
    const fetchUser = async () => {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      setUser(data);
    };

    fetchUser();
  }, [userId]); // ✅ 正确添加 userId 依赖

  // ❌ 错误示例(注释掉):
  // useEffect(async () => { ... }, [userId]); // 不能是 async

  return <div>{user?.name || 'Loading...'}</div>;
}

export default CorrectedExample;

第十一章:其他常用 Hooks

11.1 useRef Hook

名称语法用途注意事项
useRefconst ref = useRef(initialValue);创建一个可变的引用对象,其 .current 属性可存储任意可变值(如 DOM 元素、计时器 ID、任意值)。ref.current 变化不会触发重新渲染;可用于访问 DOM 或存储”实例变量”。
访问 DOM 元素useRef() + ref={ref}获取对真实 DOM 节点的引用,用于聚焦、测量等。必须将 ref 传递给 JSX 元素。

代码示例:

import React, { useRef, useEffect } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);

  const onButtonClick = () => {
    // 使用 ref.current 访问 DOM 节点
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

export default TextInputWithFocusButton;

11.2 useContext Hook

名称语法用途注意事项
useContextconst value = useContext(MyContext);订阅并读取 React 上下文(Context)的当前值。必须先通过 React.createContext 创建上下文;组件会重新渲染当上下文值变化。
消费上下文在深层嵌套组件中传递数据,避免”props 逐层传递”。适用于主题、用户认证、语言等全局状态。不要滥用,优先考虑组件组合。

代码示例:

import React, { createContext, useContext } from 'react';

// 创建上下文
const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 使用 useContext 读取当前主题
  const theme = useContext(ThemeContext);

  return <button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>I am {theme}</button>;
}

export default App;

11.3 useReducer Hook

名称语法用途注意事项
useReducerconst [state, dispatch] = useReducer(reducer, initialState);管理复杂状态逻辑,通过 dispatch(action) 触发状态更新。适用于状态逻辑复杂、有多个子值或下一次状态依赖前一次状态的场景。
Reducer 函数(state, action) => newState纯函数,根据 action 类型返回新状态。类似 Redux,但内置于 React。

代码示例:

import React, { useReducer } from 'react';

// 定义 reducer
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: action.payload };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
    </>
  );
}

export default Counter;

11.4 useCallback Hook

名称语法用途注意事项
useCallbackconst memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);缓存函数实例,避免在每次渲染时创建新函数。用于传递给子组件的回调函数,配合 React.memo 优化性能。
避免不必要的重渲染当子组件使用 React.memo 时,父组件传递的函数若每次都变,会失效。依赖数组必须包含所有在回调中使用的值。

代码示例:

import React, { useState, useCallback, memo } from 'react';

// 子组件使用 React.memo 优化
const ExpensiveComponent = memo(({ onClick, value }) => {
  console.log('ExpensiveComponent rendered');
  return <button onClick={onClick}>Count: {value}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState('');

  // ✅ 使用 useCallback 缓存函数
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 无依赖

  return (
    <div>
      <input value={otherState} onChange={e => setOtherState(e.target.value)} />
      <ExpensiveComponent onClick={handleClick} value={count} />
    </div>
  );
}

export default Parent;

11.5 useMemo Hook

名称语法用途注意事项
useMemoconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);缓存计算结果,避免在每次渲染时执行昂贵的计算。用于性能优化;不要用于副作用。
优化计算如排序、过滤大型数组、复杂数学运算。依赖数组变化时重新计算;不保证一定缓存(可能在下次渲染时重新计算)。

代码示例:

import React, { useState, useMemo } from 'react';

function SlowComponent({ list, filterText }) {
  // ✅ 使用 useMemo 缓存过滤结果
  const filteredList = useMemo(() => {
    console.log('Filtering list...');
    return list.filter(item =>
      item.toLowerCase().includes(filterText.toLowerCase())
    );
  }, [list, filterText]);

  return (
    <ul>
      {filteredList.map((item, i) => (
        <li key={i}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  const [filter, setFilter] = useState('');
  const largeList = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);

  return (
    <div>
      <input value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter" />
      <SlowComponent list={largeList} filterText={filter} />
    </div>
  );
}

export default App;

11.6 自定义 Hook 的创建与使用

名称语法用途注意事项
自定义 Hookfunction useCustomHook() { ... return value; }封装可复用的逻辑(如状态逻辑、副作用),以 use 开头命名。可以调用其他 Hook;必须在函数组件或其他自定义 Hook 中调用。
逻辑复用将跨组件的逻辑提取为独立函数,提高代码复用性和可维护性。返回值可以是任意类型(状态、函数、对象等)。

代码示例:创建并使用自定义 Hook

import React, { useState, useEffect } from 'react';

// 自定义 Hook:管理本地存储的状态
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // 同步到 localStorage
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
}

// 使用自定义 Hook
function UserPreferences() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [fontSize, setFontSize] = useLocalStorage('fontSize', 16);

  return (
    <div style={{ fontSize }}>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
      <button onClick={() => setFontSize(fontSize + 2)}>Increase Font</button>
    </div>
  );
}

export default UserPreferences;

第十二章:Context API

12.1 什么是 Context

名称语法用途注意事项
ContextReact.createContext(defaultValue)提供一种在组件树中传递数据的方式,避免”props 逐层传递”(prop drilling)。适用于主题、用户认证、语言、UI 状态等全局或跨层级数据。
数据传递跨越中间组件直接向深层后代传递数据。不需要将 props 通过每一层组件手动传递。不应滥用,仅用于真正需要全局访问的数据。

核心思想:Context 允许你”广播”数据给整个组件树,任何后代组件都可以选择”订阅”这些数据。

12.2 创建 Context

名称语法用途注意事项
createContextconst MyContext = React.createContext(defaultValue);创建一个 Context 对象。defaultValue 是当组件没有匹配的 Provider 时使用的值(可用于测试或独立使用)。
Context 对象包含 .Provider.Consumer 两个属性。用于后续提供和消费上下文值。通常将 Context 导出为模块,供其他组件使用。

代码示例:

import React from 'react';

// 创建一个主题上下文,初始值为 'light'
const ThemeContext = React.createContext('light');

// 创建一个用户上下文,初始值为 null
const UserContext = React.createContext(null);

// 导出 Context,供其他组件导入使用
export { ThemeContext, UserContext };

12.3 Provider 与 Consumer 的使用

名称语法用途注意事项
Provider<MyContext.Provider value={value}>...</MyContext.Provider>提供上下文的值,其后代组件可访问该值。value 可以是任意类型(字符串、对象、函数等)。
Consumer<MyContext.Consumer>{value => <div>{value}</div>}</MyContext.Consumer>通过 render props 模式消费上下文值。语法较繁琐,现代开发推荐使用 useContext

代码示例:

import React from 'react';
import { ThemeContext } from './context/ThemeContext';

function App() {
  const [theme, setTheme] = React.useState('dark');

  return (
    // 使用 Provider 提供主题值
    <ThemeContext.Provider value={theme}>
      <Toolbar onToggle={() => setTheme(theme === 'light' ? 'dark' : 'light')} />
    </ThemeContext.Provider>
  );
}

function Toolbar({ onToggle }) {
  return (
    <div>
      <ThemedButton />
      <button onClick={onToggle}>Toggle Theme</button>
    </div>
  );
}

// 使用 Consumer 消费主题值
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {currentTheme => (
        <button style={{ background: currentTheme === 'dark' ? '#333' : '#fff', color: currentTheme === 'dark' ? '#fff' : '#000' }}>
          I am {currentTheme}
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default App;

12.4 使用 useContext 消费 Context

名称语法用途注意事项
useContextconst value = useContext(MyContext);在函数组件中直接读取上下文的当前值。必须先导入创建的 Context 对象;组件会在上下文值变化时重新渲染。
简化消费替代 Consumer,语法更简洁,易于理解。是现代 React 中消费 Context 的首选方式。不能在类组件中使用(类组件使用 static contextType 或 Consumer)。

代码示例(优化上例):

import React, { useContext } from 'react';
import { ThemeContext } from './context/ThemeContext';

function App() {
  const [theme, setTheme] = React.useState('dark');

  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar onToggle={() => setTheme(theme === 'light' ? 'dark' : 'light')} />
    </ThemeContext.Provider>
  );
}

function Toolbar({ onToggle }) {
  return (
    <div>
      <ThemedButton />
      <button onClick={onToggle}>Toggle Theme</button>
    </div>
  );
}

// 使用 useContext 消费主题值(更简洁)
function ThemedButton() {
  const theme = useContext(ThemeContext); // ✅ 推荐

  return (
    <button style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#000' }}>
      I am {theme}
    </button>
  );
}

export default App;

12.5 Context 的性能优化

问题解决方案说明
Provider 值变化导致所有后代重新渲染将 value 对象拆分为多个 Context,或使用 useMemo 缓存 value。React 会重新渲染所有消费该 Context 的组件,即使它们只关心部分数据。
避免不必要的 Provider 重渲染将 Provider 放在稳定组件中,或使用 React.memo 包装父组件。如果 Provider 的父组件频繁更新,可能导致不必要的 Context 更新。
使用 useMemo 缓存 value<MyContext.Provider value={useMemo(() => ({ state, dispatch }), [state])}>当 value 是对象或函数时,防止每次渲染都创建新引用。

代码示例:性能优化

import React, { useState, useMemo, useContext } from 'react';

// 拆分 Context 避免过度渲染
const ThemeContext = React.createContext();
const UserContext = React.createContext();

function App() {
  const [theme, setTheme] = useState('dark');
  const [user, setUser] = useState({ name: 'Alice' });

  // ✅ 使用 useMemo 缓存 value,防止每次渲染都创建新对象
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme, setTheme]);
  const userValue = useMemo(() => ({ user, setUser }), [user, setUser]);

  return (
    <ThemeContext.Provider value={themeValue}>
      <UserContext.Provider value={userValue}>
        <Layout />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

function ThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  // 只有 theme 变化时才会重新渲染
  return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>{theme}</button>;
}

function UserGreeting() {
  const { user } = useContext(UserContext);
  // 只有 user 变化时才会重新渲染
  return <p>Hello, {user.name}!</p>;
}

12.6 使用场景与替代方案(如状态管理库)

场景/方案说明建议
适用场景主题切换✅ 推荐使用 Context
用户认证状态
多语言(i18n)
UI 状态(如侧边栏展开)
跨多层组件的配置
不适用场景高频更新的状态(如每秒多次)❌ 应考虑状态管理库
大型复杂状态树
需要时间旅行调试、中间件等高级功能
替代方案Redux:功能强大,适合大型应用,有丰富的中间件生态。根据项目复杂度选择
Zustand:轻量、简洁,基于 Hooks,易于上手。
Jotai / Recoil:原子化状态管理,与 React 模型更契合。

总结:

  • Context + useReducer 可以满足大多数中等复杂度应用的状态管理需求。
  • 对于大型或复杂应用,建议结合状态管理库(如 Redux Toolkit、Zustand)以获得更好的开发体验和性能。

第十三章:高阶组件(HOC)

13.1 HOC 的概念与作用

名称语法/模式用途注意事项
高阶组件(HOC)const EnhancedComponent = hoc(WrappedComponent);一个函数,接收一个组件并返回一个新组件。不是 React API 的一部分,而是一种模式。
作用复用组件逻辑(如数据获取、权限检查)提高代码复用性和可维护性。HOC 不应修改原组件,而应通过组合方式增强。
抽象通用行为(如加载状态、错误处理)
修改组件行为或注入 props

核心思想:HOC 类似于高阶函数,用于”包装”组件,为其添加额外功能。

13.2 编写一个 HOC

名称语法/模式用途注意事项
基本结构function withHOC(WrappedComponent) { return function EnhancedComponent(props) { return <WrappedComponent {...props} />; }; }创建一个返回新组件的函数。新组件通常称为”增强组件”(Enhanced Component)。
注入 props在返回的组件中传递额外的 props。例如注入用户信息、主题、API 数据等。避免 props 冲突,可使用命名空间或 ...rest

代码示例:创建一个日志 HOC

import React from 'react';

// HOC:记录组件的挂载和卸载
function withLogger(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} mounted`);
    }

    componentWillUnmount() {
      console.log(`Component ${WrappedComponent.name} unmounted`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用 HOC
function UserProfile({ name }) {
  return <div>User: {name}</div>;
}

const LoggedUserProfile = withLogger(UserProfile);

function App() {
  const [show, setShow] = React.useState(true);
  return (
    <div>
      <button onClick={() => setShow(!show)}>
        {show ? 'Hide' : 'Show'} Profile
      </button>
      {show && <LoggedUserProfile name="Alice" />}
    </div>
  );
}

export default App;

13.3 属性代理与反向继承

模式语法/说明用途注意事项
属性代理(Props Proxy)return <WrappedComponent {...this.props} extraProp={value} />;控制传入被包装组件的 props,可读取、修改、添加或删除 props。最常用模式;适用于大多数场景(如注入、转换 props)。
反向继承(Inheritance Inversion)class HOC extends WrappedComponent { render() { return super.render(); } }返回的组件继承自被包装组件,可访问其 state、props、生命周期等。可操作被包装组件的内部实现,但破坏封装性,不推荐。

代码示例:属性代理 vs 反向继承

import React from 'react';

// 1. 属性代理:添加 loading 状态
function withLoading(WrappedComponent) {
  return function LoadingHOC({ isLoading, ...props }) {
    if (isLoading) {
      return <div>Loading...</div>;
    }
    // ✅ 属性代理:传递剩余 props
    return <WrappedComponent {...props} />;
  };
}

// 2. 反向继承:修改渲染逻辑(不推荐)
function withConditionalRendering(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      // 可访问 super.render(),但侵入性强
      const element = super.render();
      if (this.props.condition === false) {
        return null;
      }
      return element;
    }
  };
}

// 使用
function DataComponent({ data }) {
  return <div>Data: {data}</div>;
}

const LoadingData = withLoading(DataComponent);
const ConditionalData = withConditionalRendering(DataComponent);

function App() {
  return (
    <div>
      <LoadingData isLoading={true} />
      <ConditionalData condition={false} data="Hello" />
    </div>
  );
}

export default App;

13.4 HOC 的组合

名称语法/模式用途注意事项
组合函数compose(f, g, h)(x)h(g(f(x)))将多个 HOC 组合应用到一个组件上。避免嵌套过深,提高可读性。
函数组合工具手写 compose 或使用 Lodash 的 flowRight。从右到左依次应用 HOC。React 生态中常见(如 Redux 的 connect)。

代码示例:组合多个 HOC

import React from 'react';

// 多个 HOC
function withAuth(WrappedComponent) {
  return function AuthHOC(props) {
    const [isLoggedIn] = React.useState(true);
    return isLoggedIn ? <WrappedComponent {...props} /> : <div>Please login</div>;
  };
}

function withTheme(WrappedComponent) {
  return function ThemeHOC(props) {
    const theme = 'dark';
    return <WrappedComponent {...props} theme={theme} />;
  };
}

function withLogging(WrappedComponent) {
  return function LoggingHOC(props) {
    React.useEffect(() => {
      console.log('Component rendered');
    });
    return <WrappedComponent {...props} />;
  };
}

// 组合 HOC
function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

// 应用多个 HOC
const enhance = compose(
  withLogging,
  withTheme,
  withAuth
);

function Profile({ name, theme }) {
  return <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>Profile: {name}</div>;
}

const EnhancedProfile = enhance(Profile);

function App() {
  return <EnhancedProfile name="Bob" />;
}

export default App;

13.5 HOC 的局限性与 Hooks 的对比

问题/对比说明建议
Wrapper Hell多个 HOC 嵌套导致 JSX 层级过深,调试困难。使用 Hooks 可避免组件嵌套。
命名冲突HOC 注入的 props 可能与组件原有 props 冲突。使用命名空间或 Hooks 避免。
静态方法丢失HOC 返回的新组件不继承原组件的静态方法。需手动复制,或使用 hoist-non-react-statics 库。
Refs 不被传递Refs 不能穿透 HOC,需使用 React.forwardRefHooks 天然支持 ref。
与 Hooks 对比Hooks:逻辑复用更简洁,无嵌套,支持组合✅ 现代 React 推荐优先使用 Hooks,HOC 作为补充。
HOC:适用于类组件或需要修改组件结构的场景

总结:

  • HOC 是 React 类组件时代的逻辑复用方案,功能强大但存在嵌套和冲突问题。
  • Hooks 是 React 函数组件时代的首选,更简洁、直观,推荐用于新项目。
  • 何时使用 HOC:需要操作组件生命周期、修改渲染树、或在不支持 Hooks 的旧代码中。

第十四章:React Router 基础

14.1 安装与配置 React Router

名称语法/用法用途注意事项
安装npm install react-router-dom安装 React Router 的 DOM 版本(用于 Web 应用)。确保项目已安装 reactreact-dom
配置在应用最外层包裹 <BrowserRouter>启用客户端路由,监听 URL 变化并更新 UI。通常在 index.jsApp.js 的最顶层使用。

代码示例:项目入口配置

// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

BrowserRouter 使用 HTML5 的 history.pushState API 实现 URL 导航,无需页面刷新。

组件语法/用法用途注意事项
BrowserRouter<BrowserRouter><App /></BrowserRouter>提供路由能力,管理浏览器历史记录。必须是路由相关组件的祖先。
Routes<Routes><Route path="..." element={<Component />} /></Routes>包含一组 Route,匹配并渲染第一个匹配的路由。v6 中取代了 v5 的 Switch
Route<Route path="/" element={<Home />} />定义路径与要渲染组件的映射。必须放在 Routes 内部;使用 element 属性(非 component)。
Link<Link to="/about">About</Link>创建可点击的导航链接,点击后不刷新页面。替代原生 <a> 标签,防止页面跳转。

代码示例:基础路由结构

// App.js
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';

function Home() {
  return <h2>Home Page</h2>;
}

function About() {
  return <h2>About Us</h2>;
}

function Contact() {
  return <h2>Contact Info</h2>;
}

function App() {
  return (
    <div>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>

      {/* 路由出口 */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  );
}

export default App;

14.3 动态路由与参数传递

名称语法/用法用途注意事项
动态段:paramName(如 /users/:id匹配动态路径片段。: 后为参数名。
useParamsconst { id } = useParams();在组件中读取动态路由参数。必须在 Route 渲染的组件中使用。

代码示例:用户详情页

import React from 'react';
import { Routes, Route, Link, useParams } from 'react-router-dom';

function Users() {
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
  ];

  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <Link to={`/users/${user.id}`}>{user.name}</Link>
          </li>
        ))}
      </ul>
      {/* 嵌套路由出口 */}
      <Routes>
        <Route path=":id" element={<UserProfile />} />
      </Routes>
    </div>
  );
}

function UserProfile() {
  const { id } = useParams(); // 获取动态参数
  return <p>User ID: {id}</p>;
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<div><h1>Home</h1><Link to="/users">View Users</Link></div>} />
      <Route path="/users/*" element={<Users />} />
    </Routes>
  );
}

export default App;

14.4 嵌套路由

名称语法/用法用途注意事项
嵌套路由在父组件内部使用 <Routes>path=":id"实现父子页面结构(如用户列表 → 用户详情)。父路由路径以 /* 结尾,或使用 end={false}(v6.4+)。
相对路径Link 中使用相对路径(如 to="edit"在嵌套上下文中导航。相对路径基于当前路由。

代码示例:管理用户

import React from 'react';
import { Routes, Route, Link, useParams, useMatch } from 'react-router-dom';

function UserDashboard() {
  const { id } = useParams();
  return (
    <div>
      <h3>User {id} Dashboard</h3>
      <nav>
        <Link to="profile">Profile</Link> |
        <Link to="settings">Settings</Link>
      </nav>
      <Routes>
        <Route path="profile" element={<Profile />} />
        <Route path="settings" element={<Settings />} />
      </Routes>
    </div>
  );
}

function Profile() {
  return <p>Profile Page</p>;
}

function Settings() {
  return <p>Settings Page</p>;
}

function UsersList() {
  const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            <Link to={`${user.id}/profile`}>User {user.name}</Link>
          </li>
        ))}
      </ul>
      <Routes>
        <Route path=":id/*" element={<UserDashboard />} />
      </Routes>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<h1>Home</h1>} />
      <Route path="/users/*" element={<UsersList />} />
    </Routes>
  );
}

export default App;

14.5 编程式导航(useNavigate)

名称语法/用法用途注意事项
useNavigateconst navigate = useNavigate();在函数组件中通过 JavaScript 控制导航。取代 v5 的 history.push
跳转navigate('/target')导航到指定路径。可传递 state:navigate('/page', { state: { from: 'home' } })
相对导航navigate('relative-path')在嵌套路由中跳转。基于当前路径。
回退navigate(-1)返回上一页。类似浏览器后退按钮。

代码示例:登录后跳转

import React, { useState } from 'react';
import { Routes, Route, Link, useNavigate } from 'react-router-dom';

function Login() {
  const [username, setUsername] = useState('');
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    // 模拟登录成功
    console.log('Logged in as', username);
    // 跳转到主页
    navigate('/', { replace: true }); // replace 替换当前历史记录
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Login</h2>
      <input
        value={username}
        onChange={e => setUsername(e.target.value)}
        placeholder="Username"
        required
      />
      <button type="submit">Login</button>
    </form>
  );
}

function Home() {
  return (
    <div>
      <h2>Welcome!</h2>
      <Link to="/login">Go to Login</Link>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<Login />} />
    </Routes>
  );
}

export default App;

第十五章:性能优化

15.1 使用 React.memo 进行组件记忆

名称语法/用法用途注意事项
React.memoconst MyComponent = React.memo(function MyComponent(props) { ... });高阶组件,缓存组件的渲染输出,当 props 不变时跳过重新渲染。仅适用于函数组件;默认进行浅比较,可传入自定义比较函数。
自定义比较React.memo(Component, (prevProps, nextProps) => {...})精确控制是否跳过渲染。返回 true 表示跳过渲染,false 表示重新渲染。

代码示例:避免不必要的子组件渲染

import React, { useState } from 'react';

// ✅ 使用 React.memo 优化
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('ExpensiveComponent rendered'); // 仅在 data 变化时打印
  return <div>Processed: {data.toUpperCase()}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState('');

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>

      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        placeholder="Type something"
      />

      {/* 只有 input 变化时,ExpensiveComponent 才会重新渲染 */}
      <ExpensiveComponent data={input} />
    </div>
  );
}

export default Parent;

15.2 使用 useMemo 缓存计算结果

名称语法/用法用途注意事项
useMemoconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);缓存昂贵的计算结果,避免在每次渲染时重复执行。用于性能优化;不要用于副作用。
适用场景排序、过滤大型数组、复杂数学运算、对象创建等。依赖数组必须包含所有在回调中使用的变量。

代码示例:缓存过滤结果

import React, { useState, useMemo } from 'react';

function FilteredList({ items, filter }) {
  // ✅ 使用 useMemo 缓存过滤结果
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => item.includes(filter));
  }, [items, filter]); // 依赖项

  return (
    <ul>
      {filteredItems.map((item, i) => (
        <li key={i}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  const [filterText, setFilterText] = useState('');
  const largeList = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);

  return (
    <div>
      <input
        value={filterText}
        onChange={e => setFilterText(e.target.value)}
        placeholder="Filter items"
      />
      <FilteredList items={largeList} filter={filterText} />
    </div>
  );
}

export default App;

15.3 使用 useCallback 缓存函数

名称语法/用法用途注意事项
useCallbackconst memoizedFn = useCallback(fn, deps);缓存函数实例,避免在每次渲染时创建新函数。主要用于传递给子组件的回调函数,配合 React.memo 使用。
避免重渲染当子组件使用 React.memo 时,若父组件传递的函数每次都变,会导致优化失效。依赖数组必须包含函数中使用的所有变量。

代码示例:配合 React.memo 优化

import React, { useState, useCallback, memo } from 'react';

const Child = memo(({ onClick, value }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Count: {value}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // ✅ 使用 useCallback 缓存函数
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 无依赖

  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <Child onClick={handleClick} value={count} />
    </div>
  );
}

export default Parent;

15.4 虚拟化长列表(React Virtualized)

名称语法/用法用途注意事项
虚拟化只渲染可视区域内的元素,大幅减少 DOM 节点。解决长列表(如 10000+ 项)导致的性能问题。需要固定或可预测的行高。
推荐库react-window(轻量)、react-virtualized(功能全)提供 FixedSizeListVariableSizeList 等组件。安装:npm install react-window

代码示例:使用 react-window 虚拟化列表

import React from 'react';
import { FixedSizeList as List } from 'react-window';

// 每行渲染的组件
const Row = ({ index, style }) => (
  <div style={style}>
    Item {index}
  </div>
);

function VirtualizedList() {
  const itemCount = 10000; // 模拟大量数据

  return (
    <List
      height={400}        // 容器高度
      itemCount={itemCount}
      itemSize={35}       // 每行高度
      width="100%"
    >
      {Row}
    </List>
  );
}

function App() {
  return (
    <div>
      <h2>Virtualized Long List</h2>
      <VirtualizedList />
    </div>
  );
}

export default App;

⚠️ 需先安装:npm install react-window

15.5 使用 Profiler 进行性能分析

名称语法/用法用途注意事项
<Profiler><Profiler id="..." onRender={callback}>...</Profiler>测量组件树的渲染性能,识别慢速渲染。仅在开发模式下工作;生产环境无开销。
onRender 回调(id, phase, actualDuration, baseDuration, ...)获取性能数据。actualDuration:本次渲染耗时;baseDuration:估算不使用 memo 的耗时。

代码示例:分析组件渲染性能

import React, { Profiler, useState } from 'react';

// 模拟慢速组件
function SlowComponent({ iterations }) {
  // 模拟昂贵计算
  const start = performance.now();
  while (performance.now() - start < 10) {} // 延迟 10ms

  return <div>Slow Component ({iterations} iterations)</div>;
}

// 性能分析回调
function onRenderCallback(
  id,           // 发生提交的 Profiler 树的 "id"
  phase,        // "mount" 或 "update"
  actualDuration,     // 本次更新 committed 花费的毫秒数
  baseDuration,       // 估算重新渲染整棵子树需要的总毫秒数
  startTime,    // 本次更新开始的时间
  commitTime,   // 本次更新 committed 的时间
  interactions  // 本次更新关联的 interactions
) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  });
}

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Performance Profiler</h1>

      {/* 包裹需要分析的组件 */}
      <Profiler id="SlowComponent" onRender={onRenderCallback}>
        <SlowComponent iterations={count} />
      </Profiler>

      <button onClick={() => setCount(count + 1)}>
        Rerender Slow Component (Count: {count})
      </button>
    </div>
  );
}

export default App;