本文结合 React 18 / 19 最新特性,整理了 React 面试中的 基础题、高频题、重点难点题,每道题均附带详细答案与代码示例。
相关笔记:React 基础 | React 生命周期 | React Fiber | React 性能优化 | React 组件通信
- Vue25
- 数据结构和算法20
- React12
- 项目总结10
- 源码分析9
- Node.js7
- Webpack6
- 微前端6
- JavaScript5
- 前端性能优化5
- 移动端开发5
- 前端工程化4
- 组件化4
- Axios2
- Git2
- NPM2
- Vite2
- 项目部署2
- 组件2
- 项目搭建2
- 3d1
- AI1
- WEB 安全1
- 前端存储1
- 浏览器1
- CSS1
- Docker1
- HTML1
- Linux1
- 网络1
- TypeScript1
- 版本管理1
- 前端CICD1
- 组件库1
- SEO1
- 调试技巧1
- 性能优化1
- 操作系统1
提出背景
浏览器渲染限制
浏览器的帧
页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。
上图是一帧内需要完成的任务,浏览器在一帧内可完成如下任务:
输入事件捕捉:
- 浏览器首先会接受用户的输入事件,如点击、滚动、触摸等,并准备将其转换为相应的处理逻辑。
- Blocking input events(阻塞输入事件):例如
touch或wheel- Non-blocking input events(非阻塞输入事件):例如
click或keypress事件回调执行:
- 对于已注册的事件监听器,浏览器会执行相应的事件回调函数,处理用户输入或页面状态的变化。
帧开始(Begin frame):
- 每一帧事件(Per frame events),例如
window resize、scroll或media query change样式计算和布局:
- rAF(requestAnimationFrame)
- 浏览器会解析和计算CSS样式,确定页面中每个元素的尺寸和位置。这个过程称为布局或重排。
绘制渲染(Paint):合成更新(Compositing update)、重绘部分节点(Paint invalidation)和 Record
绘制(渲染):
- 在确定了元素的样式和位置后,浏览器会开始绘制页面。
- 这包括将文本、图像和其他内容绘制到屏幕上。绘制操作可能涉及多个层级的渲染,最终合成到屏幕上显示。
合成:
- 如果页面使用了硬件加速(如CSS3D转换或WebGL),浏览器会将不同层级的渲染结果合成到一起,形成一个完整的页面图像。
空闲回调执行:如果在一帧的剩余时间内还有空闲时间,并且存在待执行的空闲回调(如RequestIdleCallback),浏览器会尝试执行这些回调。这些回调通常用于执行非关键性的、可延迟的任务。
浏览器的渲染过程受到设备刷新率的限制。一般来说,设备的刷新率是60Hz,意味着每秒钟有60帧的渲染机会。因此,浏览器需要在大约16.67毫秒(1000毫秒/60帧)内完成上述所有任务,以确保流畅的动画和用户交互。
如何提高组件的渲染效率的?在React中如何避免不必要的render?
原理
React 组件 render的触发时机:
- 类组件通过调用
setState方法触发render; - 父组件一旦发生
render渲染,子组件一定也会执行render渲染;
父组件渲染导致子组件渲染,但子组件并没有发生任何改变,此时可以从避免多余的渲染。
例如:当我们想要更新一个子组件的时候,如下图绿色部分:
单向数据流
当前组件的 state 以 props 的形式流动时,组件不会改变接收的数据,只会监听数据的变化,当数据发生变化时它们会使用接收到的新值,而不是去修改已有的值。
| 通信组件 | 方法 | 介绍 |
|---|---|---|
| 父->子 组件通信 | 父组件通过传递 Props 给子组件 | |
| 子->父 组件通信 | 子组件调用父组件函数参数: 父组件传递给子组件的是一个绑定了自身上下文的函数 那么子组件在调用该函数时就可以将想要交给父组件的数据以函数入参的形式给出去 |
|
| 兄弟组件通信 | 状态提升:将两个兄弟组件公共状态提升到公共父组件中,通过父组件 props 和 事件传递 | |
| 跨层级的组件通信 | Context API | |
| 任意组件通信 | 发布订阅机制、Redux |
React15 的生命周期
constructor()componentWillReceiveProps()shouldComponentUpdate()componentWillMount()componentWillUpdate()componentDidUpdate()componentDidMount()render()componentWillUnmount()
Babel 编译结果
Babel 可以通过特定的插件(如 @babel/plugin-transform-react-jsx)将 JSX 语法转换为 React.createElement 调用,例如:
<div>
< img src="avatar.png" className="profile" />
<Hello />
</div>
React DOM 的渲染采用 render 方法
虚拟 DOM 转成 真实 DOM 流程
创建虚拟 DOM
假设需要渲染如下真实 DOM:
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
<li class="item">Item3</li>
</ul>
React 的渲染工作流是值从组件数据改变到组件实际更新发生的过程。
-
开发者编写 JSX 代码:
- 开发者使用 JSX 语法编写用户界面的代码。
- JSX 允许开发者在 JavaScript 代码中编写类似 HTML 的结构,从而创建 React 元素和组件。
-
Babel 编译:
- JSX 代码需要被转换成浏览器可以理解的 JavaScript 代码。
- Babel 是一个 JavaScript 编译器,它可以将现代 JavaScript 代码和 JSX 转换为向后兼容的 JavaScript 代码。
- Babel 会将 JSX 标签转换为
React.createElement调用。例如,<div>Hello, world!</div>会被转换成React.createElement('div', null, 'Hello, world!')。
-
React.createElement 调用:
- 转换后的 JavaScript 代码会使用
React.createElement方法来创建 React 元素。这些元素是描述 UI 树的 JavaScript 对象。
- 转换后的 JavaScript 代码会使用
-
ReactElement 调用:
React.createElement方法返回一个ReactElement实例,它是 React 元素的内部表示。ReactElement包含了创建真实 DOM 节点所需的所有信息,如标签名、属性和子元素。
-
作为参数处理:
ReactElement实例会被作为参数传递给 React 的渲染系统。React 会使用这些元素来构建虚拟 DOM。
-
“虚拟 DOM”:
- 虚拟 DOM 是一个轻量级的 JavaScript 对象,它表示真实 DOM 的状态。
- React 使用虚拟 DOM 来提高性能,因为它允许 React 在内存中进行 UI 更新,而不是直接操作真实 DOM。
-
ReactDoM.render():
ReactDOM.render()方法是将虚拟 DOM 渲染到真实 DOM 的入口点。这个方法接收一个ReactElement和一个 DOM 节点的选择器或引用作为参数。- React 会使用 Diff 算法比较新旧虚拟 DOM 树的差异,并计算出最小的更新操作。
-
真实 DOM 传入:
- 根据计算出的更新操作,React 会更新传入的 DOM 节点。这个过程可能包括创建新的 DOM 节点、修改现有节点的属性或样式、删除不再需要的节点等。
- 最终,更新后的真实 DOM 会反映在浏览器中,用户可以看到更新后的界面。
virtual dom 是单独存在的一个库, github: https://github.com/Matt-Esch/virtual-dom
Real DOM
在认识 virtual dom 之前,先认识一下 Real DOM:
Real DOM,真实 DOM,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实 DOM 结构,如下:
Real DOM(真实DOM)是浏览器原生提供的,它表示当前页面的实际HTML结构。当页面中的数据发生变化时,Real DOM 会进行重新渲染和更新。它是我们与网页进行交互的基础,通过操作Real DOM,我们可以改变网页的内容、样式和行为。