01-React基础教程

01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架

本文基于 React 19.2 最新稳定版撰写,覆盖从核心概念到企业项目落地的完整知识链路。无论你是刚入门的前端小白,还是希望系统梳理 React 知识体系的开发者,都能在本文中找到可直接上手的实战代码与深度思考。


一、你是否正在经历这些前端开发的痛点?

文章目录

  • [01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架](#01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架)
    • 一、你是否正在经历这些前端开发的痛点?
    • [二、React 如何系统性地解决这些痛点?](#二、React 如何系统性地解决这些痛点?)
    • [三、React 到底是什么?](#三、React 到底是什么?)
      • [3.1 专业解释](#3.1 专业解释)
      • [3.2 大白话解释](#3.2 大白话解释)
      • [3.3 生活案例](#3.3 生活案例)
    • [四、为什么选择 React?它的核心优势是什么?](#四、为什么选择 React?它的核心优势是什么?)
      • [4.1 声明式编程:从"怎么做"到"要什么"](#4.1 声明式编程:从"怎么做"到"要什么")
      • [4.2 组件化:一次编写,处处复用](#4.2 组件化:一次编写,处处复用)
      • [4.3 虚拟 DOM:性能与开发体验的双赢](#4.3 虚拟 DOM:性能与开发体验的双赢)
      • [4.4 生态繁荣:你需要的一切都有现成方案](#4.4 生态繁荣:你需要的一切都有现成方案)
      • [4.5 企业背书:从 Meta 到全球顶尖公司](#4.5 企业背书:从 Meta 到全球顶尖公司)
    • [五、React 是怎么演进过来的?十三年版本史全梳理](#五、React 是怎么演进过来的?十三年版本史全梳理)
      • [5.1 萌芽期(2011-2013):从内部工具到开源](#5.1 萌芽期(2011-2013):从内部工具到开源)
      • [5.2 成长期(2014-2016):生态建立与核心完善](#5.2 成长期(2014-2016):生态建立与核心完善)
      • [5.3 变革期(2017-2019):Fiber 架构与 Hooks 革命](#5.3 变革期(2017-2019):Fiber 架构与 Hooks 革命)
      • [5.4 稳定期(2020-2021):渐进升级与并发准备](#5.4 稳定期(2020-2021):渐进升级与并发准备)
      • [5.5 并发时代(2022-2023):Concurrent Rendering 正式落地](#5.5 并发时代(2022-2023):Concurrent Rendering 正式落地)
      • [5.6 全栈时代(2024-至今):服务器组件与 React Compiler](#5.6 全栈时代(2024-至今):服务器组件与 React Compiler)
    • [六、React 怎么用?从零基础到企业级实战](#六、React 怎么用?从零基础到企业级实战)
      • [6.1 环境搭建:用 Vite 快速创建项目](#6.1 环境搭建:用 Vite 快速创建项目)
      • [6.2 第一个 React 组件:Hello World](#6.2 第一个 React 组件:Hello World)
      • [6.3 状态管理:useState 详解](#6.3 状态管理:useState 详解)
      • [6.4 副作用处理:useEffect 详解](#6.4 副作用处理:useEffect 详解)
      • [6.5 组件通信:Props、Context 与状态提升](#6.5 组件通信:Props、Context 与状态提升)
        • [6.5.1 父传子:Props](#6.5.1 父传子:Props)
        • [6.5.2 跨层级通信:Context API](#6.5.2 跨层级通信:Context API)
      • [6.6 企业项目实战:搭建一个可扩展的 React 后台管理系统](#6.6 企业项目实战:搭建一个可扩展的 React 后台管理系统)
        • [6.6.1 项目目录结构](#6.6.1 项目目录结构)
        • [6.6.2 Axios 请求封装](#6.6.2 Axios 请求封装)
        • [6.6.3 API 服务层封装](#6.6.3 API 服务层封装)
        • [6.6.4 自定义 Hook:数据请求逻辑复用](#6.6.4 自定义 Hook:数据请求逻辑复用)
        • [6.6.5 页面组件:用户管理列表页](#6.6.5 页面组件:用户管理列表页)
        • [6.6.6 路由配置与权限守卫](#6.6.6 路由配置与权限守卫)
    • [七、React vs Vue vs Angular:三大前端框架深度对比](#七、React vs Vue vs Angular:三大前端框架深度对比)
      • [7.1 核心定位与设计哲学对比](#7.1 核心定位与设计哲学对比)
      • [7.2 技术特性对比](#7.2 技术特性对比)
      • [7.3 生态与社区对比](#7.3 生态与社区对比)
      • [7.4 性能对比](#7.4 性能对比)
      • [7.5 选型建议](#7.5 选型建议)
    • [八、React 的常用应用场景全景](#八、React 的常用应用场景全景)
      • [8.1 企业后台管理系统](#8.1 企业后台管理系统)
      • [8.2 电商平台前端](#8.2 电商平台前端)
      • [8.3 社交媒体应用](#8.3 社交媒体应用)
      • [8.4 数据可视化大屏](#8.4 数据可视化大屏)
      • [8.5 移动端跨平台应用](#8.5 移动端跨平台应用)
      • [8.6 SSR/全栈应用](#8.6 SSR/全栈应用)
    • 九、面试官高频面试题精选与解析
      • [9.1 基础概念类](#9.1 基础概念类)
      • [9.2 Hooks 原理类](#9.2 Hooks 原理类)
      • [9.3 渲染机制类](#9.3 渲染机制类)
      • [9.4 状态管理类](#9.4 状态管理类)
      • [9.5 性能优化类](#9.5 性能优化类)
      • [9.6 高级特性类](#9.6 高级特性类)
    • 十、总结与学习路径建议

在正式进入 React 的世界之前,我们先聊聊每一个前端开发者都绕不开的真实痛点。如果你正在经历以下任何一种场景,那么这篇文章就是为你而写。

痛点一:DOM 操作让代码变成一锅粥

小李是一名刚入行的前端开发,接到一个需求:做一个待办事项列表,支持新增、删除、标记完成、筛选全部/未完成/已完成。他用原生 JavaScript 写了两百多行代码,里面充斥着 document.getElementById、createElement、appendChild、removeChild。每加一个功能,就要手动去更新 DOM 节点的文本、类名、属性。三天后产品经理说要加一个"批量删除已完成项"的功能,小李发现自己已经完全看不懂当初写的 DOM 操作逻辑了,改一处 bug 引出三处新 bug。

本质问题:当应用状态变得复杂时,手动维护状态与 DOM 之间的同步关系,是一项极易出错且维护成本极高的工作。

痛点二:页面逻辑耦合严重,改一个功能要翻遍整个文件

小王维护着一个运营后台系统,所有的页面逻辑都写在一个巨大的 index.html 文件里。用户管理、订单管理、数据统计的 JavaScript 代码混杂在一起,变量名互相冲突,函数之间隐式依赖。每次新增一个菜单页面,都要小心翼翼地避免影响已有功能。团队想做代码复用,发现根本拆不出来------因为每一段逻辑都和页面上的具体 DOM 结构深度绑定。

本质问题:缺乏组件化的代码组织方式,导致逻辑耦合、复用困难、团队协作效率低下。

痛点三:数据变化了,界面没有同步更新

小张在做一个实时数据看板,后端通过 WebSocket 推送最新的销售数据。他写了一个定时器每隔一秒去读取全局变量然后更新页面,但经常出现数据已经变了、界面还停留在旧值的情况。更糟糕的是,多个组件依赖同一份数据,他不得不在每个地方都写一遍更新逻辑,稍有遗漏就出现数据不一致。

本质问题:缺少响应式的数据驱动机制,开发者需要手动追踪数据变化并触发界面更新,既繁琐又不可靠。

痛点四:团队技术栈不统一,新人上手成本极高

某中型互联网公司的前端团队,A 项目用 jQuery,B 项目用 Vue 2,C 项目用 AngularJS,D 项目刚启动还在纠结用什么。每个项目的架构风格、目录结构、编码规范完全不同。新员工入职后,光熟悉各个项目的写法就要花一个月。更严重的是,公司想沉淀一套通用组件库,发现根本无法跨项目复用------因为每个框架的组件编写方式完全不同。

本质问题:缺少一个生态成熟、社区活跃、企业广泛采用的统一技术栈,导致团队协作和知识沉淀成本居高不下。


二、React 如何系统性地解决这些痛点?

面对上述痛点,React 给出的不是头痛医头的局部修补,而是一套从底层设计理念到工程实践的完整解决方案:

痛点 React 的解决方案 核心机制
DOM 操作繁琐 声明式渲染 你只需要描述"界面应该长什么样",React 自动帮你完成 DOM 更新
代码耦合严重 组件化架构 将 UI 拆分为独立、可复用的组件,每个组件管理自己的逻辑和视图
数据不同步 响应式状态管理 state 变化自动触发组件重新渲染,数据与界面始终保持一致
技术栈不统一 成熟生态与社区 全球最大的前端生态,企业级方案丰富,人才供给充足

接下来,我们将从"是什么"到"怎么用",逐层揭开 React 的面纱。


三、React 到底是什么?

3.1 专业解释

React(官方名称为 React.js 或 ReactJS)是由 Meta(原 Facebook)开发并开源的一个用于构建用户界面的 JavaScript 库。它于 2013 年 5 月在 JSConf US 上由 Jordan Walke 正式发布,目前最新稳定版本为 React 19.2(2025 年 10 月发布),并于 2026 年 2 月正式成立 React Foundation,由 Linux 基金会托管。

React 的核心定位非常明确:它只负责视图层(View),不是一个完整的 MVC 框架。它不提供路由、不提供 HTTP 客户端、不提供表单验证------这些都交给生态中的其他库来完成。这种"专注做一件事并做到极致"的设计哲学,正是 React 能够保持灵活性和生命力的关键所在。

React 的三大核心支柱:

  1. 组件(Component):React 应用的基本构建单元。可以是函数组件(Function Component)或类组件(Class Component),现代 React 开发以函数组件配合 Hooks 为绝对主流。
  2. 状态(State)与属性(Props):组件内部通过 state 管理可变数据,通过 props 接收父组件传递的数据。数据流向是单向的------从父组件流向子组件。
  3. 虚拟 DOM 与协调(Reconciliation):React 在内存中维护一棵虚拟 DOM 树,当状态变化时,通过 Diff 算法对比新旧虚拟 DOM 树的差异,然后以最小代价更新真实 DOM。从 React 16 开始,这一机制被 Fiber 架构重构,实现了可中断、可恢复的异步渲染。

3.2 大白话解释

如果把开发一个网页应用比作装修一套房子:

  • 原生 JavaScript 就像你自己拿着锤子、钉子、油漆,一砖一瓦地手动施工。墙刷错了颜色要自己铲掉重刷,家具位置变了要自己搬来搬去。
  • React 就像你请了一个智能装修管家。你只需要告诉它"我想要客厅是北欧风格,沙发放在靠窗位置,墙上挂三幅画",管家就会自动帮你安排工人、采购材料、完成施工。如果后来你想把沙发换个位置,只需要重新说一句"沙发移到电视墙对面",管家会自动计算需要挪动哪些东西、怎么挪动最省力,然后帮你搞定。

你不需要关心具体怎么操作 DOM,你只需要描述"在当前数据下,界面应该是什么样子"。React 会帮你搞定从数据到界面的一切。

3.3 生活案例

想象你是一家餐厅的老板。

没有 React 的日子:每来一桌客人,你都要亲自跑到厨房告诉厨师做什么菜,跑到吧台告诉调酒师调什么饮料,跑到前台告诉收银员收多少钱。客人中途加菜、换桌、退菜,你都要亲自跑一趟去更新各个岗位的信息。高峰期你会忙得焦头烂额,而且很容易出错------比如厨房已经做了菜,但收银员还不知道这桌加了菜。

有了 React 的日子:你只需要维护一张"当前订单状态表"。客人点了什么菜、坐在哪桌、是否已上菜,全部记录在这张表里。厨房里有一个显示屏自动同步显示最新订单,吧台有一个显示屏自动显示需要调的饮料,收银台自动显示应收金额。客人加菜了,你只需要在订单状态表里加一行,所有岗位的显示屏会自动更新。你再也不用跑来跑去,所有岗位看到的信息永远是一致的。

这张"订单状态表"就是 React 中的 state,那些自动同步的显示屏就是 React 组件。你只管维护数据,React 负责让界面和数据保持同步。


四、为什么选择 React?它的核心优势是什么?

4.1 声明式编程:从"怎么做"到"要什么"

传统的命令式编程要求开发者详细描述"怎么做"------先获取这个元素,再修改它的文本,然后添加一个类名。而 React 的声明式编程只要求开发者描述"要什么"------在这个数据状态下,界面应该渲染成什么样子。

这种思维方式的转变,极大地降低了认知负担。你不再需要在脑海中模拟每一步 DOM 操作的顺序和影响,只需要关注数据和界面之间的映射关系。

4.2 组件化:一次编写,处处复用

React 将界面拆分为一个个独立的组件。每个组件是一个自包含的单元,拥有自己的状态、逻辑和视图。组件可以嵌套组合,可以像搭积木一样构建复杂的界面。

一个设计良好的按钮组件,可以用在登录页、注册页、表单页、弹窗里------不需要写四遍代码。这就是组件化带来的复用价值。

4.3 虚拟 DOM:性能与开发体验的双赢

直接操作真实 DOM 是非常昂贵的,因为浏览器的重排(Reflow)和重绘(Repaint)代价很高。React 通过在内存中维护虚拟 DOM 树,将多次状态变化合并后计算出最小的 DOM 更新集合,然后一次性批量更新真实 DOM。

这意味着开发者可以放心地频繁更新状态,而不需要手动做性能优化------React 会在底层帮你处理好。

4.4 生态繁荣:你需要的一切都有现成方案

React 拥有全球最大的前端生态系统:

  • 路由:React Router、TanStack Router
  • 状态管理:Redux Toolkit、Zustand、Jotai、Recoil
  • 服务端状态:React Query(TanStack Query)、SWR
  • UI 组件库:Ant Design、Material UI、Chakra UI、shadcn/ui
  • 全栈框架:Next.js、Remix、Gatsby
  • 移动端:React Native、Expo
  • 开发工具:React DevTools、React Compiler v1.0

根据 2025 年 Stack Overflow 开发者调查,React 连续多年位居"最常用的 Web 框架"前列,全球范围内的企业采用率和人才供给量均为前端框架之首。

4.5 企业背书:从 Meta 到全球顶尖公司

React 诞生于 Meta 内部,被用于 Facebook、Instagram、WhatsApp、Messenger 等亿级用户产品。除 Meta 外,使用 React 的知名公司包括但不限于:Netflix、Airbnb、Uber、Twitter/X、Discord、Shopify、Atlassian、Dropbox、Slack、字节跳动、阿里巴巴、腾讯等。

一个被如此多高并发、高复杂度的产品验证过的技术栈,其可靠性和可扩展性是毋庸置疑的。


五、React 是怎么演进过来的?十三年版本史全梳理

理解 React 的演进历史,有助于我们理解每一个设计决策背后的考量。下面是 React 从诞生到今天的完整时间线。

5.1 萌芽期(2011-2013):从内部工具到开源

React 的故事始于 2011 年。当时 Facebook 的工程师 Jordan Walke 在开发 Facebook 信息流(News Feed)时,深感传统的 DOM 操作方式难以应对复杂的交互需求。他创造了一个名为 FaxJS 的原型,这就是 React 的前身。

2012 年,Facebook 收购 Instagram 后,React 被应用到 Instagram 的 Web 端,这是 React 第一次在 Facebook 之外的产品中得到验证。

2013 年 5 月 29 日,Jordan Walke 在 JSConf US 上正式开源 React,发布 v0.3.0 版本。当时业界的反应是怀疑和嘲讽------"在 JavaScript 里写 HTML?这不是倒退吗?"但时间证明了这个设计的前瞻性。

5.2 成长期(2014-2016):生态建立与核心完善

  • v0.14(2015 年 10 月) :引入函数组件(Function Component)的概念,允许开发者用纯函数的方式编写无状态组件。同时将 React 拆分为 react 和 react-dom 两个包,为 React Native 的跨平台能力铺路。
  • v15.0(2016 年 4 月) :完善组件生命周期方法,引入 PropTypes 类型校验,推出 React DevTools 调试工具。这一阶段的 React 已经具备了现代前端框架的基本形态,但底层的同步渲染机制在大型应用中开始暴露性能瓶颈。

5.3 变革期(2017-2019):Fiber 架构与 Hooks 革命

  • v16.0(2017 年 9 月):这是 React 历史上最重要的版本之一。React 16 用 Fiber 架构完全重写了底层的协调(Reconciliation)引擎。Fiber 将渲染任务拆分为小的工作单元,支持可中断、可恢复、可优先级调度的异步渲染。同时引入了 Error Boundaries(错误边界)、Portals(传送门)、Fragments(片段)等实用特性。
  • v16.3(2018 年 3 月) :引入新的 Context API(解决了旧 Context 性能问题)、createRef、forwardRef、Profiler 组件。
  • v16.6(2018 年 10 月) :引入 React.lazy() 和 Suspense,支持代码分割和异步加载。
  • v16.8(2019 年 2 月) :Hooks 正式发布。这是 React 历史上最具革命性的 API 变化。Hooks 允许函数组件使用 state 和生命周期等特性,彻底改变了 React 的开发范式。从此,函数组件成为 React 开发的绝对主流,类组件逐步退出历史舞台。

5.4 稳定期(2020-2021):渐进升级与并发准备

  • v17.0(2020 年 10 月):React 17 是一个"没有新特性"的版本。它的核心目标是让版本升级变得更容易------改进了事件委托机制(从 document 改为 root 节点)、优化了 useEffect 的清理时机、支持了新的 JSX 转换(不再需要手动 import React)。React 17 为后续的并发特性奠定了基础。

5.5 并发时代(2022-2023):Concurrent Rendering 正式落地

  • v18.0(2022 年 3 月) :并发渲染(Concurrent Rendering)正式稳定。引入了 createRoot(替代 ReactDOM.render)、自动批处理(Automatic Batching)、startTransition、useTransition、useDeferredValue、Suspense 改进、流式 SSR 等一系列并发特性。React 18 标志着 React 正式进入并发时代,界面响应性得到质的提升。

5.6 全栈时代(2024-至今):服务器组件与 React Compiler

  • v19.0(2024 年 12 月) :React 服务器组件(React Server Components)正式稳定。引入了 Actions(异步动作处理)、useActionState、useOptimistic、useFormStatus、use() API 等新特性。React 19 重新定义了 React 的边界------它不再仅仅是一个客户端 UI 库,而是开始向全栈渲染框架演进。
  • React Compiler v1.0(2025 年 10 月) :React 编译器正式发布稳定版。它是一个构建时工具,通过自动记忆化(Automatic Memoization)优化 React 应用性能,开发者不再需要手动编写 useMemo、useCallback、memo。该编译器已在 Meta 的大型应用中经过实战检验,完全可用于生产环境。
  • v19.2(2025 年 10 月) :引入 <Activity /> 组件(管理可见性)、useEffectEvent(从 Effects 中触发事件)、性能追踪(Performance Tracks)、部分预渲染(Partial Pre-rendering)等新特性。
  • React Foundation(2026 年 2 月):React 基金会正式成立,由 Linux 基金会托管,标志着 React 进入更加开放和社区驱动的治理新阶段。

六、React 怎么用?从零基础到企业级实战

6.1 环境搭建:用 Vite 快速创建项目

React 官方已于 2025 年 2 月正式宣布逐步淘汰 Create React App(CRA),推荐使用框架(如 Next.js)或 Vite 来创建项目。对于学习和中小型项目,Vite 是最佳选择。

bash 复制代码
# 使用 Vite 创建 React + TypeScript 项目
npm create vite@latest my-react-app -- --template react-ts

# 进入项目目录
cd my-react-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

项目启动后,默认运行在 http://localhost:5173。

项目目录结构如下:

text 复制代码
my-react-app/
├── public/              # 静态资源
├── src/
│   ├── assets/          # 资源文件
│   ├── App.css          # 根组件样式
│   ├── App.tsx          # 根组件
│   ├── index.css        # 全局样式
│   ├── main.tsx         # 应用入口
│   └── vite-env.d.ts    # Vite 类型声明
├── index.html           # HTML 模板
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

6.2 第一个 React 组件:Hello World

React 组件本质上就是一个返回 JSX 的 JavaScript 函数。

tsx 复制代码
// src/components/HelloWorld.tsx
import React from 'react';

// 函数组件:接收 props,返回 JSX
interface HelloWorldProps {
  name: string;
}

const HelloWorld: React.FC<HelloWorldProps> = ({ name }) => {
  return (
    <div className="hello-container">
      <h1>Hello, {name}!</h1>
      <p>欢迎来到 React 的世界</p>
    </div>
  );
};

export default HelloWorld;

在根组件中使用:

tsx 复制代码
// src/App.tsx
import HelloWorld from './components/HelloWorld';

function App() {
  return (
    <div className="App">
      <HelloWorld name="羽师" />
    </div>
  );
}

export default App;

6.3 状态管理:useState 详解

useState 是最基础也是最常用的 Hook,用于在函数组件中添加状态。

tsx 复制代码
// src/components/Counter.tsx
import { useState } from 'react';

const Counter = () => {
  // 声明一个状态变量 count,初始值为 0
  // setCount 是更新状态的函数
  const [count, setCount] = useState<number>(0);

  // 函数式更新:当新状态依赖旧状态时,推荐使用函数式更新
  const increment = () => {
    setCount((prev) => prev + 1);
  };

  const decrement = () => {
    setCount((prev) => prev - 1);
  };

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

  return (
    <div className="counter">
      <h2>计数器</h2>
      <p className="count-display">当前计数:{count}</p>
      <div className="button-group">
        <button onClick={decrement}>-1</button>
        <button onClick={reset}>重置</button>
        <button onClick={increment}>+1</button>
      </div>
    </div>
  );
};

export default Counter;

6.4 副作用处理:useEffect 详解

useEffect 用于处理组件中的副作用(Side Effect),例如数据请求、订阅、手动操作 DOM 等。

tsx 复制代码
// src/components/UserProfile.tsx
import { useState, useEffect } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

const UserProfile = ({ userId }: { userId: number }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    // 标记组件是否已卸载,防止内存泄漏
    let isMounted = true;

    const fetchUser = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`
        );

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data: User = await response.json();

        if (isMounted) {
          setUser(data);
        }
      } catch (err) {
        if (isMounted) {
          setError(err instanceof Error ? err.message : '未知错误');
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };

    fetchUser();

    // 清理函数:组件卸载或依赖变化时执行
    return () => {
      isMounted = false;
    };
  }, [userId]); // 依赖数组:userId 变化时重新执行 effect

  if (loading) {
    return <div className="loading">加载中...</div>;
  }

  if (error) {
    return <div className="error">加载失败:{error}</div>;
  }

  return (
    <div className="user-profile">
      <h2>{user?.name}</h2>
      <p>邮箱:{user?.email}</p>
      <p>用户 ID:{user?.id}</p>
    </div>
  );
};

export default UserProfile;

6.5 组件通信:Props、Context 与状态提升

6.5.1 父传子:Props
tsx 复制代码
// 子组件
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
  variant = 'primary',
  disabled = false,
}) => {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {label}
    </button>
  );
};

// 父组件使用
const Parent = () => {
  const handleClick = () => {
    console.log('按钮被点击了');
  };

  return (
    <div>
      <Button label="提交" onClick={handleClick} variant="primary" />
      <Button label="取消" onClick={() => {}} variant="secondary" />
    </div>
  );
};
6.5.2 跨层级通信:Context API

当数据需要在多层嵌套的组件中共享时,逐层传递 props 会非常繁琐(prop drilling)。Context API 可以优雅地解决这个问题。

tsx 复制代码
// src/context/ThemeContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

// 定义 Context 的类型
interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

// 创建 Context,设置默认值
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

// 创建 Provider 组件
interface ThemeProviderProps {
  children: ReactNode;
}

export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// 自定义 Hook:简化 Context 的使用
export const useTheme = (): ThemeContextType => {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme 必须在 ThemeProvider 内部使用');
  }
  return context;
};

在应用入口包裹 Provider:

tsx 复制代码
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from './context/ThemeContext';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ThemeProvider>
      <App />
    </ThemeProvider>
  </React.StrictMode>
);

在任意子组件中使用:

tsx 复制代码
// src/components/ThemeToggle.tsx
import { useTheme } from '../context/ThemeContext';

const ThemeToggle = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme} className="theme-toggle">
      当前主题:{theme === 'light' ? '浅色' : '深色'}
    </button>
  );
};

export default ThemeToggle;

6.6 企业项目实战:搭建一个可扩展的 React 后台管理系统

在真实的企业项目中,一个良好的项目架构是可维护性和可扩展性的基础。下面我们以 Feature-Based(按功能模块划分)的架构模式,搭建一个企业级 React 后台管理系统。

6.6.1 项目目录结构
text 复制代码
src/
├── assets/              # 静态资源(图片、字体、图标)
├── components/          # 全局通用组件(与业务无关)
│   ├── Button/
│   ├── Modal/
│   ├── Table/
│   └── Form/
├── config/              # 配置文件
│   ├── env.ts           # 环境变量
│   └── constants.ts     # 全局常量
├── features/            # 业务功能模块(核心目录)
│   ├── auth/            # 认证模块
│   │   ├── components/  # 模块内组件
│   │   ├── hooks/       # 模块内自定义 Hook
│   │   ├── services/    # 模块内 API 服务
│   │   ├── types/       # 模块内类型定义
│   │   ├── pages/       # 模块内页面
│   │   └── index.ts     # 模块导出入口
│   ├── dashboard/       # 仪表盘模块
│   ├── user-management/ # 用户管理模块
│   └── order-management/ # 订单管理模块
├── hooks/               # 全局自定义 Hook
│   ├── useLocalStorage.ts
│   ├── useDebounce.ts
│   └── usePagination.ts
├── layouts/             # 布局组件
│   ├── AdminLayout.tsx
│   └── AuthLayout.tsx
├── lib/                 # 第三方库封装
│   ├── axios.ts         # Axios 实例封装
│   └── react-query.ts   # React Query 配置
├── pages/               # 页面级组件(与路由映射)
├── router/              # 路由配置
│   └── index.tsx
├── store/               # 全局状态管理
│   └── useAuthStore.ts
├── styles/              # 全局样式
├── types/               # 全局类型定义
├── utils/               # 工具函数
│   ├── format.ts
│   ├── validate.ts
│   └── storage.ts
├── App.tsx              # 根组件
└── main.tsx             # 应用入口
6.6.2 Axios 请求封装
typescript 复制代码
// src/lib/axios.ts
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';

// 创建 Axios 实例
const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器:自动添加 Token
apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = localStorage.getItem('access_token');
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

// 响应拦截器:统一处理错误
apiClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response.data;
  },
  (error: AxiosError) => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          // Token 过期,清除登录状态并跳转登录页
          localStorage.removeItem('access_token');
          window.location.href = '/login';
          break;
        case 403:
          console.error('没有权限访问该资源');
          break;
        case 404:
          console.error('请求的资源不存在');
          break;
        case 500:
          console.error('服务器内部错误');
          break;
        default:
          console.error('请求失败:', error.message);
      }
    } else if (error.request) {
      console.error('网络错误,请检查网络连接');
    }
    return Promise.reject(error);
  }
);

export default apiClient;
6.6.3 API 服务层封装
typescript 复制代码
// src/features/user-management/services/userService.ts
import apiClient from '../../../lib/axios';

// 定义用户相关的类型
export interface User {
  id: number;
  username: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  status: 'active' | 'disabled';
  createdAt: string;
}

export interface UserListParams {
  page: number;
  pageSize: number;
  keyword?: string;
  role?: string;
  status?: string;
}

export interface UserListResponse {
  list: User[];
  total: number;
  page: number;
  pageSize: number;
}

export interface CreateUserDto {
  username: string;
  email: string;
  password: string;
  role: User['role'];
}

export interface UpdateUserDto extends Partial<Omit<CreateUserDto, 'password'>> {
  status?: User['status'];
}

// 用户相关 API
export const userService = {
  // 获取用户列表
  getUserList: (params: UserListParams): Promise<UserListResponse> => {
    return apiClient.get('/users', { params });
  },

  // 获取用户详情
  getUserById: (id: number): Promise<User> => {
    return apiClient.get(`/users/${id}`);
  },

  // 创建用户
  createUser: (data: CreateUserDto): Promise<User> => {
    return apiClient.post('/users', data);
  },

  // 更新用户
  updateUser: (id: number, data: UpdateUserDto): Promise<User> => {
    return apiClient.put(`/users/${id}`, data);
  },

  // 删除用户
  deleteUser: (id: number): Promise<void> => {
    return apiClient.delete(`/users/${id}`);
  },

  // 批量删除用户
  batchDeleteUsers: (ids: number[]): Promise<void> => {
    return apiClient.post('/users/batch-delete', { ids });
  },
};
6.6.4 自定义 Hook:数据请求逻辑复用
typescript 复制代码
// src/features/user-management/hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService, UserListParams, CreateUserDto, UpdateUserDto } from '../services/userService';

// 查询用户列表的 Hook
export const useUsers = (params: UserListParams) => {
  return useQuery({
    queryKey: ['users', params],
    queryFn: () => userService.getUserList(params),
    staleTime: 5 * 60 * 1000, // 5 分钟内数据视为新鲜
    keepPreviousData: true, // 分页切换时保留旧数据
  });
};

// 创建用户的 Hook
export const useCreateUser = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: CreateUserDto) => userService.createUser(data),
    onSuccess: () => {
      // 创建成功后,使用户列表缓存失效,触发重新获取
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
};

// 更新用户的 Hook
export const useUpdateUser = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }: { id: number; data: UpdateUserDto }) =>
      userService.updateUser(id, data),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
      queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
    },
  });
};

// 删除用户的 Hook
export const useDeleteUser = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: number) => userService.deleteUser(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
};
6.6.5 页面组件:用户管理列表页
tsx 复制代码
// src/features/user-management/pages/UserListPage.tsx
import { useState } from 'react';
import { Table, Button, Input, Select, Modal, message, Space, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { useUsers, useDeleteUser } from '../hooks/useUsers';
import type { User } from '../services/userService';
import UserFormModal from '../components/UserFormModal';

const UserListPage = () => {
  // 搜索和分页状态
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(10);
  const [keyword, setKeyword] = useState('');
  const [role, setRole] = useState<string | undefined>();
  const [status, setStatus] = useState<string | undefined>();

  // 弹窗状态
  const [modalVisible, setModalVisible] = useState(false);
  const [editingUser, setEditingUser] = useState<User | null>(null);

  // 使用自定义 Hook 获取数据
  const { data, isLoading, isFetching } = useUsers({
    page,
    pageSize,
    keyword: keyword || undefined,
    role,
    status,
  });

  const deleteMutation = useDeleteUser();

  // 表格列定义
  const columns: ColumnsType<User> = [
    {
      title: 'ID',
      dataIndex: 'id',
      width: 80,
    },
    {
      title: '用户名',
      dataIndex: 'username',
    },
    {
      title: '邮箱',
      dataIndex: 'email',
    },
    {
      title: '角色',
      dataIndex: 'role',
      render: (role: User['role']) => {
        const colorMap = {
          admin: 'red',
          editor: 'blue',
          viewer: 'green',
        };
        const textMap = {
          admin: '管理员',
          editor: '编辑者',
          viewer: '查看者',
        };
        return <Tag color={colorMap[role]}>{textMap[role]}</Tag>;
      },
    },
    {
      title: '状态',
      dataIndex: 'status',
      render: (status: User['status']) => (
        <Tag color={status === 'active' ? 'green' : 'default'}>
          {status === 'active' ? '启用' : '禁用'}
        </Tag>
      ),
    },
    {
      title: '创建时间',
      dataIndex: 'createdAt',
      render: (date: string) => new Date(date).toLocaleString('zh-CN'),
    },
    {
      title: '操作',
      key: 'action',
      width: 180,
      render: (_, record) => (
        <Space>
          <Button
            type="link"
            size="small"
            onClick={() => {
              setEditingUser(record);
              setModalVisible(true);
            }}
          >
            编辑
          </Button>
          <Button
            type="link"
            size="small"
            danger
            onClick={() => {
              Modal.confirm({
                title: '确认删除',
                content: `确定要删除用户「${record.username}」吗?此操作不可恢复。`,
                okText: '确认删除',
                cancelText: '取消',
                okButtonProps: { danger: true },
                onOk: async () => {
                  try {
                    await deleteMutation.mutateAsync(record.id);
                    message.success('删除成功');
                  } catch {
                    message.error('删除失败');
                  }
                },
              });
            }}
          >
            删除
          </Button>
        </Space>
      ),
    },
  ];

  // 重置搜索条件
  const handleReset = () => {
    setKeyword('');
    setRole(undefined);
    setStatus(undefined);
    setPage(1);
  };

  return (
    <div className="user-list-page">
      {/* 搜索栏 */}
      <div className="search-bar" style={{ marginBottom: 16 }}>
        <Space wrap>
          <Input
            placeholder="搜索用户名/邮箱"
            value={keyword}
            onChange={(e) => setKeyword(e.target.value)}
            style={{ width: 200 }}
            allowClear
          />
          <Select
            placeholder="选择角色"
            value={role}
            onChange={setRole}
            style={{ width: 150 }}
            allowClear
            options={[
              { value: 'admin', label: '管理员' },
              { value: 'editor', label: '编辑者' },
              { value: 'viewer', label: '查看者' },
            ]}
          />
          <Select
            placeholder="选择状态"
            value={status}
            onChange={setStatus}
            style={{ width: 150 }}
            allowClear
            options={[
              { value: 'active', label: '启用' },
              { value: 'disabled', label: '禁用' },
            ]}
          />
          <Button type="primary" onClick={() => setPage(1)}>
            搜索
          </Button>
          <Button onClick={handleReset}>重置</Button>
        </Space>
      </div>

      {/* 操作栏 */}
      <div className="action-bar" style={{ marginBottom: 16 }}>
        <Button
          type="primary"
          onClick={() => {
            setEditingUser(null);
            setModalVisible(true);
          }}
        >
          新增用户
        </Button>
      </div>

      {/* 数据表格 */}
      <Table
        columns={columns}
        dataSource={data?.list || []}
        rowKey="id"
        loading={isLoading || isFetching}
        pagination={{
          current: page,
          pageSize,
          total: data?.total || 0,
          showSizeChanger: true,
          showQuickJumper: true,
          showTotal: (total) => `共 ${total} 条记录`,
          onChange: (page, pageSize) => {
            setPage(page);
            setPageSize(pageSize);
          },
        }}
      />

      {/* 新增/编辑弹窗 */}
      <UserFormModal
        visible={modalVisible}
        editingUser={editingUser}
        onClose={() => setModalVisible(false)}
        onSuccess={() => setModalVisible(false)}
      />
    </div>
  );
};

export default UserListPage;
6.6.6 路由配置与权限守卫
tsx 复制代码
// src/router/index.tsx
import { createBrowserRouter, Navigate } from 'react-router-dom';
import AdminLayout from '../layouts/AdminLayout';
import AuthLayout from '../layouts/AuthLayout';
import LoginPage from '../features/auth/pages/LoginPage';
import DashboardPage from '../features/dashboard/pages/DashboardPage';
import UserListPage from '../features/user-management/pages/UserListPage';
import OrderListPage from '../features/order-management/pages/OrderListPage';
import RequireAuth from '../components/RequireAuth';

const router = createBrowserRouter([
  {
    path: '/auth',
    element: <AuthLayout />,
    children: [
      { path: 'login', element: <LoginPage /> },
    ],
  },
  {
    path: '/',
    element: (
      <RequireAuth>
        <AdminLayout />
      </RequireAuth>
    ),
    children: [
      { index: true, element: <Navigate to="/dashboard" replace /> },
      { path: 'dashboard', element: <DashboardPage /> },
      { path: 'users', element: <UserListPage /> },
      { path: 'orders', element: <OrderListPage /> },
    ],
  },
  {
    path: '*',
    element: <div>404 - 页面不存在</div>,
  },
]);

export default router;
tsx 复制代码
// src/components/RequireAuth.tsx
import { ReactNode } from 'react';
import { Navigate, useLocation } from 'react-router-dom';

interface RequireAuthProps {
  children: ReactNode;
}

const RequireAuth: React.FC<RequireAuthProps> = ({ children }) => {
  const location = useLocation();
  const token = localStorage.getItem('access_token');

  if (!token) {
    // 未登录,重定向到登录页,并记录来源路径
    return <Navigate to="/auth/login" state={{ from: location }} replace />;
  }

  return <>{children}</>;
};

export default RequireAuth;

七、React vs Vue vs Angular:三大前端框架深度对比

选择前端框架是每个团队和项目都要面对的决策。下面从多个维度对 React、Vue、Angular 三大主流框架进行全面对比。

7.1 核心定位与设计哲学对比

对比维度 React Vue Angular
官方定位 用于构建用户界面的 JavaScript 库 渐进式 JavaScript 框架 企业级前端应用开发平台
开发公司 Meta(原 Facebook) 尤雨溪及社区(现由 Vue 团队维护) Google
设计哲学 专注视图层,灵活组合,一切皆 JavaScript 渐进式,易用优先,平衡灵活性与规范性 全功能,强约定,工程化优先
模板语法 JSX(JavaScript 中写 HTML) SFC 单文件组件(template + script + style) TypeScript + 装饰器 + HTML 模板
学习曲线 中等(JSX + Hooks + 生态选择) 低(模板语法接近原生 HTML,文档友好) 高(TypeScript + RxJS + 依赖注入 + 模块系统)

7.2 技术特性对比

对比维度 React Vue Angular
响应式原理 状态变化触发重新渲染,虚拟 DOM Diff Proxy 代理 + 虚拟 DOM,细粒度响应式 Zone.js 变更检测 + 脏检查
组件类型 函数组件(主流)、类组件(遗留) 选项式 API、组合式 API(主流) 组件类(基于 TypeScript 装饰器)
状态管理 无内置,生态丰富(Redux/Zustand/Jotai) Pinia(官方推荐)、Vuex(旧版) NgRx(Redux 模式)、Signals(新)
路由方案 React Router(社区) Vue Router(官方) Angular Router(内置)
HTTP 客户端 无内置(Axios/Fetch) 无内置(Axios/Fetch) HttpClient(内置)
表单处理 无内置(React Hook Form/Formik) 无内置(VeeValidate) Reactive Forms(内置,功能强大)
SSR 方案 Next.js、Remix Nuxt Angular Universal
TypeScript 支持 良好(需手动配置) 良好(组合式 API 原生支持) 原生(TypeScript 是一等公民)

7.3 生态与社区对比

对比维度 React Vue Angular
GitHub Stars 约 22.5 万+ 约 20.8 万+ 约 9.5 万+
npm 周下载量 约 2500 万+ 约 500 万+ 约 400 万+
生态丰富度 极高(全球最大前端生态) 高(中文社区活跃,国内采用率高) 中高(企业级方案完整,第三方库相对少)
UI 组件库 Ant Design、MUI、Chakra、shadcn/ui 等 Element Plus、Ant Design Vue、Naive UI 等 Angular Material、NG-ZORRO、PrimeNG 等
移动端方案 React Native(成熟) Weex(已停止维护)、UniApp(第三方) NativeScript、Ionic
人才市场 全球需求量最大,岗位最多 国内需求量大,中小公司采用率高 大型企业和金融行业采用较多

7.4 性能对比

对比维度 React Vue Angular
运行时包体积(gzip) 约 42KB(react + react-dom) 约 34KB(Vue 3 runtime) 约 130KB+(框架体积较大)
首次渲染性能 良好(并发渲染优化) 优秀(细粒度响应式,Vapor Mode 预览中) 良好(AOT 编译优化)
更新性能 良好(虚拟 DOM + Fiber 调度) 优秀(Proxy 精确追踪依赖) 良好(Signals 优化中)
内存占用 中等 低 较高

7.5 选型建议

场景 推荐框架 理由
大型企业级应用,团队规模大 Angular 或 React Angular 的强约定和工程化适合大团队协作;React 的生态和人才储备更充足
中小型项目,快速迭代 Vue 或 React Vue 上手快、开发效率高;React 灵活度高,生态选择多
需要 SEO 和 SSR 的内容型网站 React(Next.js)或 Vue(Nuxt) Next.js 和 Nuxt 都是成熟的全栈框架,SSR 支持完善
移动端跨平台需求 React(React Native) React Native 是最成熟的跨平台移动开发方案
国内中小公司/创业团队 Vue 中文文档完善,上手快,国内社区活跃,招聘成本低
全球市场/海外项目 React 全球采用率最高,社区资源最丰富,人才供给最充足
金融/政企等强规范行业 Angular TypeScript 原生支持,强类型和强约定适合合规要求高的项目

八、React 的常用应用场景全景

8.1 企业后台管理系统

这是 React 最广泛的应用场景之一。后台管理系统通常具有复杂的表单、表格、权限控制、数据可视化等需求,React 的组件化能力和 Ant Design 等成熟 UI 组件库的配合,能够极大提升开发效率。

典型功能模块:用户管理、角色权限、订单管理、数据统计仪表盘、内容管理、系统配置。

8.2 电商平台前端

电商平台对页面交互流畅度、商品展示效果、购物车状态同步、订单流程体验都有很高要求。React 的响应式更新机制和虚拟 DOM 优化,能够保证在大量商品数据和频繁交互下的流畅体验。

典型功能模块:商品列表与筛选、商品详情、购物车、结算流程、订单追踪、个人中心。

8.3 社交媒体应用

社交媒体应用的核心是实时动态流、用户互动、消息通知。React 的组件化架构非常适合构建高度交互的社交界面,配合 WebSocket 和实时数据同步方案,可以实现流畅的社交体验。

典型功能模块:动态信息流、评论与点赞、实时消息、用户主页、关注系统、通知中心。

8.4 数据可视化大屏

数据可视化大屏需要处理大量实时数据的渲染和更新,对性能要求极高。React 配合 ECharts、D3.js、Recharts 等可视化库,可以构建出专业级的数据大屏。React 18 的并发渲染特性,能够保证在大量数据更新时的界面响应性。

典型功能模块:实时数据监控、KPI 指标展示、地理信息可视化、趋势分析图表、告警系统。

8.5 移动端跨平台应用

通过 React Native,开发者可以使用 React 的语法编写原生 iOS 和 Android 应用。React Native 已经被 Meta、Microsoft、Shopify、Discord 等公司用于生产环境的移动应用中。配合 Expo 工具链,可以进一步简化开发和发布流程。

典型应用类型:社交应用、电商应用、内容消费应用、工具类应用。

8.6 SSR/全栈应用

通过 Next.js、Remix 等全栈框架,React 已经从纯客户端 UI 库演进为全栈开发方案。React 19 的服务器组件(React Server Components)正式稳定,使得在 React 中直接编写服务端逻辑成为可能。这对于需要 SEO 优化、首屏性能提升、数据安全的应用尤为重要。

典型应用类型:内容网站、博客、电商站点、营销落地页、需要 SEO 的 SaaS 产品。


九、面试官高频面试题精选与解析

9.1 基础概念类

面试题 1:React 中的 state 和 props 有什么区别?

对比维度 state(状态) props(属性)
数据归属 组件内部私有数据 父组件传递给子组件的数据
可变性 组件内部可以通过 setState 修改 只读,子组件不能直接修改
作用 管理组件内部的可变状态 父组件向子组件传递数据和回调函数
触发渲染 state 变化触发当前组件重新渲染 props 变化触发子组件重新渲染
默认值 可以在 useState 中设置初始值 可以通过 defaultProps 或参数默认值设置

一句话总结:state 是组件自己的"内部账本",自己可以改;props 是父组件给的"外部指令",只能读不能改。


面试题 2:什么是虚拟 DOM?它的工作原理是什么?

虚拟 DOM(Virtual DOM)是 React 在内存中维护的一棵 JavaScript 对象树,它是真实 DOM 的轻量级抽象表示。

工作原理分为三步:

  1. 生成:每次组件渲染时,React 会根据当前的 state 和 props 生成一棵新的虚拟 DOM 树。
  2. 对比(Diff):React 将新的虚拟 DOM 树与上一次的虚拟 DOM 树进行对比,计算出差异(最小更新集合)。
  3. 更新(Patch):React 将计算出的差异批量应用到真实 DOM 上,完成界面更新。

虚拟 DOM 的价值不在于"比直接操作 DOM 更快",而在于:

  • 它提供了声明式的编程模型,开发者不需要手动操作 DOM
  • 它将多次状态更新合并为一次批量 DOM 操作,减少重排重绘
  • 它是跨平台的基础------虚拟 DOM 可以渲染到浏览器、移动端(React Native)、甚至服务端

面试题 3:React 的 Diff 算法遵循哪些策略?

React 的 Diff 算法为了在性能和准确性之间取得平衡,遵循以下三个策略:

  1. 同层比较:只对同一层级的节点进行比较,不跨层级比较。如果一个节点从父级移到了子级,React 会认为旧节点被删除、新节点被创建,而不是执行移动操作。

  2. 不同类型即替换 :如果两个节点的类型不同(比如从 div 变成了 span,或者从组件 A 变成了组件 B),React 会直接销毁旧节点并创建新节点,不会继续比较它们的子节点。

  3. 列表用 key 标识:对于同层级的列表节点,React 通过 key 属性来标识每个节点的身份。有了 key,React 就能准确判断哪些节点是新增的、哪些是删除的、哪些是移动的,从而最小化 DOM 操作。


9.2 Hooks 原理类

面试题 4:useEffect 的依赖数组有什么作用?如果不传会怎样?

useEffect 的依赖数组(dependency array)用于控制 effect 函数的执行时机:

  • 传入空数组 [] :effect 只在组件挂载时执行一次,卸载时执行清理函数。等价于类组件的 componentDidMount + componentWillUnmount。
  • 传入依赖 [a, b]:effect 在组件挂载时执行一次,之后每当 a 或 b 的值发生变化时重新执行。
  • 不传依赖数组:effect 在每次组件渲染后都会执行。这通常不是你想要的,可能导致性能问题或无限循环。

依赖数组的比较使用 Object.is 进行浅比较。需要注意的是,如果依赖是对象或数组,每次渲染都会创建新的引用,导致 effect 不必要地重复执行。


面试题 5:为什么不能在条件语句或循环中调用 Hook?

React 依赖 Hook 的调用顺序来正确地将 state 与组件对应起来。React 内部用一个链表或数组来存储每个组件的 Hook 状态,按照调用顺序依次排列。

如果在条件语句中调用 Hook,当条件的真假发生变化时,Hook 的调用顺序就会改变,导致 React 无法正确地将状态与 Hook 对应起来,从而出现状态错乱的 bug。

tsx 复制代码
// 错误示例:不要这样写
function BadExample({ showCount }) {
  if (showCount) {
    const [count, setCount] = useState(0); // 条件中的 Hook
  }
  const [name, setName] = useState(''); // 当 showCount 从 true 变为 false 时,这里的状态会错乱
  // ...
}

正确的做法是将条件判断放在 Hook 调用之后:

tsx 复制代码
// 正确示例
function GoodExample({ showCount }) {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  if (!showCount) {
    return <div>没有计数</div>;
  }

  return <div>{count}</div>;
}

面试题 6:useMemo 和 useCallback 有什么区别?分别在什么场景下使用?

对比维度 useMemo useCallback
缓存内容 缓存计算结果(值) 缓存函数引用
返回值 返回 memoized 的值 返回 memoized 的函数
本质 useMemo(() => fn, deps) 等价于 useCallback(fn, deps) 的值版本 是 useMemo(() => fn, deps) 的语法糖

使用场景:

  • useMemo:当一个计算开销很大(比如复杂的数据处理、大数组的过滤排序),且其依赖不经常变化时,用 useMemo 缓存计算结果,避免每次渲染都重新计算。
  • useCallback:当一个函数作为 props 传递给子组件,且子组件使用了 React.memo 进行优化时,用 useCallback 缓存函数引用,避免子组件因为函数引用变化而不必要地重新渲染。

需要注意的是,useMemo 和 useCallback 本身也有性能开销,不要盲目使用。只有在确实存在性能问题时,才需要使用它们进行优化。React Compiler v1.0 发布后,这些手动优化将逐渐被编译器自动处理。


9.3 渲染机制类

面试题 7:什么是 Fiber 架构?它解决了什么问题?

Fiber 是 React 16 引入的新的协调(Reconciliation)引擎,它完全重写了 React 15 及之前的同步渲染机制。

在 React 15 及之前,渲染过程是同步的、不可中断的。一旦开始渲染,就必须一口气执行完,期间浏览器无法响应用户交互。如果组件树很深、渲染任务很重,就会导致页面卡顿、掉帧。

Fiber 架构的核心改进:

  1. 可中断的渲染:Fiber 将渲染任务拆分为一个个小的工作单元(Fiber 节点),每个工作单元执行完后,React 会检查是否有更高优先级的任务需要处理。如果有,就暂停当前渲染,先处理高优先级任务,之后再恢复。
  2. 优先级调度:不同的更新可以有不同的优先级。比如用户输入的优先级高于数据加载完成后的更新,React 会优先处理用户输入,保证界面响应性。
  3. 增量渲染:渲染任务可以分帧完成,每一帧只执行一部分工作单元,将耗时控制在 16ms 以内(60fps),避免长时间阻塞主线程。
  4. 支持并发特性:Fiber 是 React 18 并发渲染(Concurrent Rendering)、Suspense、startTransition 等特性的基础。

面试题 8:React 中的 key 有什么作用?为什么不能用 index 作为 key?

key 是 React 用于标识列表中每个元素身份的特殊属性。在 Diff 算法中,React 通过 key 来判断哪些元素是新增的、哪些是删除的、哪些是位置变化的,从而最小化 DOM 操作。

不推荐用数组 index 作为 key 的原因:

当列表发生变化(比如在中间插入一个元素、删除一个元素、排序)时,元素的 index 会发生变化,导致 key 与元素的对应关系错乱。React 会错误地认为某些元素被修改了,而实际上它们只是位置变了,这会导致不必要的 DOM 操作,甚至出现状态错乱(比如输入框的值错位)。

tsx 复制代码
// 不推荐:用 index 作为 key
{list.map((item, index) => (
  <ListItem key={index} item={item} />
))}

// 推荐:用数据的唯一 ID 作为 key
{list.map((item) => (
  <ListItem key={item.id} item={item} />
))}

只有在列表是静态的(不会发生增删改排序)且没有唯一 ID 时,才可以考虑用 index 作为 key。


9.4 状态管理类

面试题 9:Context API 存在什么性能问题?如何优化?

Context API 的性能问题在于:当 Provider 的 value 发生变化时,所有消费该 Context 的组件都会重新渲染,无论它们使用的那部分数据是否真的变了。

常见的性能问题和优化方案:

  1. value 引用变化导致不必要的重渲染:
tsx 复制代码
// 问题:每次 Provider 父组件渲染时,都会创建新的 value 对象
<ThemeContext.Provider value={{ theme, toggleTheme }}>
  {children}
</ThemeContext.Provider>

// 优化:用 useMemo 缓存 value
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);
<ThemeContext.Provider value={value}>
  {children}
</ThemeContext.Provider>
  1. 高频变化数据与低频变化数据混在一个 Context 中:

将变化频率不同的数据拆分到不同的 Context 中,避免高频数据变化导致使用低频数据的组件也重新渲染。

tsx 复制代码
// 拆分前:用户信息和实时通知在一个 Context 中
// 拆分后:
<UserContext.Provider value={user}>  {/* 低频变化 */}
  <NotificationContext.Provider value={notifications}>  {/* 高频变化 */}
    {children}
  </NotificationContext.Provider>
</UserContext.Provider>
  1. 使用选择器模式(Selector):

通过自定义 Hook 实现类似 Redux 的选择器功能,只有当组件关心的那部分数据变化时才触发重渲染。可以使用 use-context-selector 库来实现。

  1. 对于复杂的全局状态,考虑使用专门的状态管理库:

Zustand、Jotai、Redux Toolkit 等状态管理库在性能优化方面比 Context API 更成熟,适合中大型应用的全局状态管理。


面试题 10:受控组件和非受控组件有什么区别?

对比维度 受控组件(Controlled) 非受控组件(Uncontrolled)
数据来源 表单数据由 React state 控制 表单数据由 DOM 自身管理
获取值方式 直接从 state 中读取 通过 ref 获取 DOM 元素的值
实时验证 支持,可以在 onChange 中实时验证 不支持,只能在提交时验证
动态控制 可以动态修改表单值(比如重置、联动) 不方便动态控制
代码量 较多(需要为每个字段写 state 和 onChange) 较少
适用场景 需要实时验证、动态联动、复杂表单逻辑 简单表单、一次性提交、与非 React 代码集成
tsx 复制代码
// 受控组件示例
const [inputValue, setInputValue] = useState('');

<input
  type="text"
  value={inputValue}
  onChange={(e) => setInputValue(e.target.value)}
/>

// 非受控组件示例
const inputRef = useRef<HTMLInputElement>(null);

const handleSubmit = () => {
  console.log(inputRef.current?.value);
};

<input type="text" ref={inputRef} />

9.5 性能优化类

面试题 11:React 性能优化的常见手段有哪些?

React 性能优化可以从以下几个层面入手:

  1. 组件层面:

    • 使用 React.memo 包裹纯展示组件,避免 props 未变化时的不必要重渲染
    • 使用 useMemo 缓存昂贵的计算结果
    • 使用 useCallback 缓存传递给子组件的函数引用
    • 合理拆分组件,将频繁变化的部分隔离到独立组件中,减少重渲染范围
  2. 状态层面:

    • 状态下放(State Colocation):将状态放在尽可能低的组件层级,避免全局状态变化导致大面积重渲染
    • 拆分 Context:将变化频率不同的数据拆分到不同的 Context 中
    • 使用专门的状态管理库(Zustand/Jotai/Redux Toolkit)替代 Context 管理复杂全局状态
  3. 渲染层面:

    • 使用 useTransition 和 useDeferredValue 标记非紧急更新,避免阻塞用户交互
    • 列表渲染使用正确的 key
    • 避免在渲染过程中创建新对象/新函数作为 props 传递
  4. 代码分割层面:

    • 使用 React.lazy() 和 Suspense 进行路由级代码分割
    • 对大型组件库(如 ECharts、富文本编辑器)进行按需加载
    • 使用 import() 动态加载非首屏必需的模块
  5. 列表虚拟化:

    • 对于长列表(超过 100 条),使用 react-window 或 react-virtuoso 进行虚拟滚动,只渲染可视区域内的元素
  6. 构建层面:

    • 启用 React Compiler v1.0 自动记忆化优化
    • 生产环境构建(关闭开发模式检查)
    • 资源压缩和 gzip/brotli 压缩
    • 预加载关键资源

面试题 12:React 18 的并发渲染(Concurrent Rendering)是什么?它解决了什么问题?

并发渲染是 React 18 引入的核心特性,它的本质是:React 可以同时准备多个版本的 UI,在渲染过程中可以中断、暂停、恢复甚至放弃渲染任务。

在 React 18 之前,渲染是同步的、不可中断的。一旦开始渲染,就必须执行完,期间浏览器无法响应用户交互。如果渲染任务很重(比如大型列表的更新),就会导致页面卡顿、输入延迟。

并发渲染解决的核心问题:

  1. 保持界面响应性:高优先级的更新(比如用户输入、点击)可以打断低优先级的更新(比如搜索结果渲染、数据加载),确保用户交互始终流畅。
  2. 避免不必要的渲染:如果一个更新在完成之前就被另一个更新取代了,React 可以直接放弃旧的渲染任务,不做无用功。
  3. Suspense 集成:并发渲染是 Suspense 数据获取、流式 SSR 等特性的基础。

React 18 提供的并发相关 API:

  • startTransition:将一个更新标记为非紧急的过渡更新
  • useTransition:startTransition 的 Hook 版本,返回过渡状态
  • useDeferredValue:延迟一个值的更新,让紧急更新先执行
  • Suspense:在组件等待异步数据时显示降级 UI

9.6 高级特性类

面试题 13:React Server Components(RSC)是什么?它和 SSR 有什么区别?

React Server Components(RSC,服务器组件)是 React 19 正式稳定的新特性,它允许组件在服务器端渲染,并且不会发送对应的 JavaScript 到客户端。

RSC 与传统 SSR(服务端渲染)的区别:

对比维度 传统 SSR React Server Components
渲染位置 每次请求时在服务器渲染为 HTML 可以在构建时或请求时在服务器渲染
客户端 JS 组件的 JS 仍然会发送到客户端(hydration) 服务器组件的 JS 不会发送到客户端
交互能力 hydration 后组件可以交互 服务器组件本身不能交互(没有事件处理、不能用 state/effect)
数据获取 需要在 useEffect 或 getServerSideProps 中获取 可以直接在组件中使用 async/await 获取数据
访问服务器资源 不能直接访问数据库、文件系统 可以直接访问数据库、文件系统、后端 API
组件类型 所有组件最终都是客户端组件 分为服务器组件(默认)和客户端组件('use client' 标记)

RSC 的核心优势:

  1. 零 bundle 体积:服务器组件的代码不会发送到客户端,显著减少 JavaScript 包体积
  2. 直接访问后端资源:可以直接在组件中查询数据库、读取文件,不需要额外的 API 层
  3. 自动代码分割:服务器组件可以动态决定加载哪些客户端组件,实现更精细的代码分割
  4. 更好的安全性:敏感逻辑(如 API 密钥、数据库查询)只在服务器执行,不会暴露给客户端

面试题 14:React Compiler 是什么?它会如何改变 React 开发?

React Compiler 是 React 团队开发的构建时优化工具,于 2025 年 10 月发布 v1.0 稳定版。它的核心功能是自动记忆化(Automatic Memoization) ------在构建时自动分析组件代码,为需要优化的地方自动添加相当于 useMemo、useCallback、memo 的优化,而不需要开发者手动编写。

React Compiler 解决的核心痛点:

  1. 消除手动优化的心智负担:开发者不再需要纠结"这里要不要加 useMemo""这个函数要不要用 useCallback 包裹",编译器会自动判断并优化。
  2. 避免手动优化的错误:手动编写 useMemo/useCallback 时,经常出现依赖数组写错、过度优化(反而增加开销)、该优化的地方没优化等问题,编译器可以避免这些错误。
  3. 统一优化标准:团队中不同开发者对性能优化的理解和习惯不同,编译器提供了统一的、经过验证的优化策略。

React Compiler 的工作原理:

它在构建时对组件代码进行静态分析,遵循 React 的规则(Rules of React),自动识别可以记忆化的值和函数,并在编译产物中注入相应的优化逻辑。它已经在 Meta 的大型应用(如 Facebook、Instagram)中经过了实战检验,完全可用于生产环境。

对开发者的影响:

  • 新的 React 项目可以默认启用 React Compiler,不再需要手动编写 useMemo/useCallback/memo
  • 现有项目可以渐进式接入,编译器可以只优化指定的文件或目录
  • 开发者仍然需要理解 React 的渲染机制和性能原理,因为编译器不是万能的,它只能在遵循 React 规则的前提下进行优化

十、总结与学习路径建议

经过以上全面的梳理,我们从痛点出发,深入了解了 React 的核心概念、演进历史、实战用法、竞品对比、应用场景和面试考点。下面给出一条清晰的学习路径建议:

第一阶段:基础入门(1-2 周)

  • 掌握 JSX 语法、组件定义、props 传递
  • 熟练使用 useState、useEffect 两个核心 Hook
  • 理解条件渲染、列表渲染、事件处理
  • 完成一个待办事项应用

第二阶段:进阶掌握(2-3 周)

  • 学习全部内置 Hook(useContext、useReducer、useMemo、useCallback、useRef 等)
  • 理解组件通信方式(props、Context、状态提升)
  • 学习 React Router 路由管理
  • 学习表单处理(受控组件、React Hook Form)
  • 完成一个带路由和表单的多页面应用

第三阶段:工程化实战(3-4 周)

  • 学习状态管理(Zustand 或 Redux Toolkit)
  • 学习服务端状态管理(React Query)
  • 学习 TypeScript 与 React 的结合
  • 学习项目架构设计(Feature-Based 架构)
  • 学习性能优化手段
  • 完成一个企业级后台管理系统

第四阶段:高级特性(持续学习)

  • 学习 React 18 并发特性(startTransition、Suspense)
  • 学习 React Server Components
  • 学习 Next.js 全栈开发
  • 学习 React Compiler
  • 参与开源项目或阅读 React 源码

React 的生态在不断演进,但核心思想------声明式、组件化、数据驱动------始终未变。掌握了这些核心思想,你就能在快速变化的前端世界中立于不败之地。


转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。

相关推荐
颜进强1 小时前
25 · NestJs DurableProviders 持久化 Provider:把 ContextId 当缓存键
前端·后端·ai编程
爱丶不疚1 小时前
Electron: Sentry 都采集了什么?你知道吗
前端·electron
恋猫de小郭1 小时前
Dart 4.0 要彻底移除 dart:mirrors,Augmentations 应该要来了
android·前端·flutter
流水白开1 小时前
React的Virtual DOM、Diff算法和Fiber
前端·react.js
乘风gg1 小时前
Spec Kit vs OpenSpec vs Superpowers:我为什么最后自己搭了一套
前端·ai编程·claude
我的xiaodoujiao1 小时前
Django 基础知识详细图文教程 14-Django 表单定义与使用
开发语言·后端·python·django
颜进强1 小时前
24 · NestJs LazyLoadingModules 懒加载模块:你在前端天天 `import()` 懒路由,但 Nest 偏偏不能懒加载路由
前端·后端·ai编程