
01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架
本文基于 React 19.2 最新稳定版撰写,覆盖从核心概念到企业项目落地的完整知识链路。无论你是刚入门的前端小白,还是希望系统梳理 React 知识体系的开发者,都能在本文中找到可直接上手的实战代码与深度思考。
一、你是否正在经历这些前端开发的痛点?
文章目录
- [01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架](#01、React 基础教程:从零基础到企业级实战,一篇文章讲透前端第一框架)
-
- 一、你是否正在经历这些前端开发的痛点?
-
- [痛点一:DOM 操作让代码变成一锅粥](#痛点一:DOM 操作让代码变成一锅粥)
- 痛点二:页面逻辑耦合严重,改一个功能要翻遍整个文件
- 痛点三:数据变化了,界面没有同步更新
- 痛点四:团队技术栈不统一,新人上手成本极高
- [二、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 的三大核心支柱:
- 组件(Component):React 应用的基本构建单元。可以是函数组件(Function Component)或类组件(Class Component),现代 React 开发以函数组件配合 Hooks 为绝对主流。
- 状态(State)与属性(Props):组件内部通过 state 管理可变数据,通过 props 接收父组件传递的数据。数据流向是单向的------从父组件流向子组件。
- 虚拟 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 团队维护) | |
| 设计哲学 | 专注视图层,灵活组合,一切皆 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 的轻量级抽象表示。
工作原理分为三步:
- 生成:每次组件渲染时,React 会根据当前的 state 和 props 生成一棵新的虚拟 DOM 树。
- 对比(Diff):React 将新的虚拟 DOM 树与上一次的虚拟 DOM 树进行对比,计算出差异(最小更新集合)。
- 更新(Patch):React 将计算出的差异批量应用到真实 DOM 上,完成界面更新。
虚拟 DOM 的价值不在于"比直接操作 DOM 更快",而在于:
- 它提供了声明式的编程模型,开发者不需要手动操作 DOM
- 它将多次状态更新合并为一次批量 DOM 操作,减少重排重绘
- 它是跨平台的基础------虚拟 DOM 可以渲染到浏览器、移动端(React Native)、甚至服务端
面试题 3:React 的 Diff 算法遵循哪些策略?
React 的 Diff 算法为了在性能和准确性之间取得平衡,遵循以下三个策略:
-
同层比较:只对同一层级的节点进行比较,不跨层级比较。如果一个节点从父级移到了子级,React 会认为旧节点被删除、新节点被创建,而不是执行移动操作。
-
不同类型即替换 :如果两个节点的类型不同(比如从
div变成了span,或者从组件 A 变成了组件 B),React 会直接销毁旧节点并创建新节点,不会继续比较它们的子节点。 -
列表用 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 架构的核心改进:
- 可中断的渲染:Fiber 将渲染任务拆分为一个个小的工作单元(Fiber 节点),每个工作单元执行完后,React 会检查是否有更高优先级的任务需要处理。如果有,就暂停当前渲染,先处理高优先级任务,之后再恢复。
- 优先级调度:不同的更新可以有不同的优先级。比如用户输入的优先级高于数据加载完成后的更新,React 会优先处理用户输入,保证界面响应性。
- 增量渲染:渲染任务可以分帧完成,每一帧只执行一部分工作单元,将耗时控制在 16ms 以内(60fps),避免长时间阻塞主线程。
- 支持并发特性: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 的组件都会重新渲染,无论它们使用的那部分数据是否真的变了。
常见的性能问题和优化方案:
- 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>
- 高频变化数据与低频变化数据混在一个 Context 中:
将变化频率不同的数据拆分到不同的 Context 中,避免高频数据变化导致使用低频数据的组件也重新渲染。
tsx
// 拆分前:用户信息和实时通知在一个 Context 中
// 拆分后:
<UserContext.Provider value={user}> {/* 低频变化 */}
<NotificationContext.Provider value={notifications}> {/* 高频变化 */}
{children}
</NotificationContext.Provider>
</UserContext.Provider>
- 使用选择器模式(Selector):
通过自定义 Hook 实现类似 Redux 的选择器功能,只有当组件关心的那部分数据变化时才触发重渲染。可以使用 use-context-selector 库来实现。
- 对于复杂的全局状态,考虑使用专门的状态管理库:
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 性能优化可以从以下几个层面入手:
-
组件层面:
- 使用
React.memo包裹纯展示组件,避免 props 未变化时的不必要重渲染 - 使用
useMemo缓存昂贵的计算结果 - 使用
useCallback缓存传递给子组件的函数引用 - 合理拆分组件,将频繁变化的部分隔离到独立组件中,减少重渲染范围
- 使用
-
状态层面:
- 状态下放(State Colocation):将状态放在尽可能低的组件层级,避免全局状态变化导致大面积重渲染
- 拆分 Context:将变化频率不同的数据拆分到不同的 Context 中
- 使用专门的状态管理库(Zustand/Jotai/Redux Toolkit)替代 Context 管理复杂全局状态
-
渲染层面:
- 使用
useTransition和useDeferredValue标记非紧急更新,避免阻塞用户交互 - 列表渲染使用正确的 key
- 避免在渲染过程中创建新对象/新函数作为 props 传递
- 使用
-
代码分割层面:
- 使用
React.lazy()和Suspense进行路由级代码分割 - 对大型组件库(如 ECharts、富文本编辑器)进行按需加载
- 使用
import()动态加载非首屏必需的模块
- 使用
-
列表虚拟化:
- 对于长列表(超过 100 条),使用
react-window或react-virtuoso进行虚拟滚动,只渲染可视区域内的元素
- 对于长列表(超过 100 条),使用
-
构建层面:
- 启用 React Compiler v1.0 自动记忆化优化
- 生产环境构建(关闭开发模式检查)
- 资源压缩和 gzip/brotli 压缩
- 预加载关键资源
面试题 12:React 18 的并发渲染(Concurrent Rendering)是什么?它解决了什么问题?
并发渲染是 React 18 引入的核心特性,它的本质是:React 可以同时准备多个版本的 UI,在渲染过程中可以中断、暂停、恢复甚至放弃渲染任务。
在 React 18 之前,渲染是同步的、不可中断的。一旦开始渲染,就必须执行完,期间浏览器无法响应用户交互。如果渲染任务很重(比如大型列表的更新),就会导致页面卡顿、输入延迟。
并发渲染解决的核心问题:
- 保持界面响应性:高优先级的更新(比如用户输入、点击)可以打断低优先级的更新(比如搜索结果渲染、数据加载),确保用户交互始终流畅。
- 避免不必要的渲染:如果一个更新在完成之前就被另一个更新取代了,React 可以直接放弃旧的渲染任务,不做无用功。
- 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 的核心优势:
- 零 bundle 体积:服务器组件的代码不会发送到客户端,显著减少 JavaScript 包体积
- 直接访问后端资源:可以直接在组件中查询数据库、读取文件,不需要额外的 API 层
- 自动代码分割:服务器组件可以动态决定加载哪些客户端组件,实现更精细的代码分割
- 更好的安全性:敏感逻辑(如 API 密钥、数据库查询)只在服务器执行,不会暴露给客户端
面试题 14:React Compiler 是什么?它会如何改变 React 开发?
React Compiler 是 React 团队开发的构建时优化工具,于 2025 年 10 月发布 v1.0 稳定版。它的核心功能是自动记忆化(Automatic Memoization) ------在构建时自动分析组件代码,为需要优化的地方自动添加相当于 useMemo、useCallback、memo 的优化,而不需要开发者手动编写。
React Compiler 解决的核心痛点:
- 消除手动优化的心智负担:开发者不再需要纠结"这里要不要加 useMemo""这个函数要不要用 useCallback 包裹",编译器会自动判断并优化。
- 避免手动优化的错误:手动编写 useMemo/useCallback 时,经常出现依赖数组写错、过度优化(反而增加开销)、该优化的地方没优化等问题,编译器可以避免这些错误。
- 统一优化标准:团队中不同开发者对性能优化的理解和习惯不同,编译器提供了统一的、经过验证的优化策略。
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 的生态在不断演进,但核心思想------声明式、组件化、数据驱动------始终未变。掌握了这些核心思想,你就能在快速变化的前端世界中立于不败之地。
转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。