这篇文章汇总了 ReactJS 中值得深入研究的高级概念。读完后,不仅在前端面试中能更胸有成竹,还能自行开发一个类似 ReactJS 的 UI 库。
目录
- Rendering 的含义与过程
- Re-rendering 发生的机制及原因
- Virtual DOM 的原理
- Reconciliation 算法的运行方式
- ReactJS 的性能优化方案
1. 什么是 Rendering?它是如何进行的?
在 React 中,我们常提到 “渲染(Rendering)”。本质上,它是把 JSX 或通过 React.createElement()
生成的元素转换为实际的 DOM 节点,让页面在浏览器中展现出来。
JSX 与 React.createElement()
JSX(JavaScript XML)是一种 React 引入的语法糖。浏览器只能理解 JavaScript,所以 JSX 需要先经过 Babel 编译成 React.createElement()
的调用,才会生成所谓的 “React Element”(一个纯粹的 JavaScript 对象)。
示例:
例 1
例 2
例 3
例 4
React.createElement(type, props, ...children)
会返回一个描述 DOM 结构的 JS 对象,如:
React 最终会根据这些对象来构造真实 DOM。
初次渲染(Initial Rendering)
初次渲染的流程大致是:
- React 组件(函数式/类)返回 JSX
- Babel 将其转换为 React Element
- React 构建出一份虚拟的 DOM 结构(Virtual DOM)
- React 将虚拟 DOM 与真实 DOM 同步,页面上出现相应的节点
大型应用通常有成百上千个组件嵌套,最终 React 会构建出巨大的虚拟 DOM 树,再将其 “映射” 到真实 DOM。初次加载时生成的真实 DOM 较多,耗时也更多。
2. 什么是 Re-rendering,组件何时会重新渲染?
Re-rendering 指组件为了更新 UI,会再次执行渲染过程。React 只在需要时重新渲染,而不是盲目全量刷新,以提高效率。
触发重新渲染的场景
- State 变化
当useState
或this.setState
更新了 state,组件会重新渲染。
- Props 改变
如果父组件传递的新 props 和旧 props 不同,子组件会重新渲染。
- 父组件重渲染
只要父组件重新渲染,即使子组件的 props 没变,子组件也默认跟着渲染。
点按钮后,父组件因为 state 改变而重渲染,Child 也跟着渲染。如果不想子组件重复渲染,可以使用 React.memo(Child)
,阻止不必要的更新。
React 18+ 中的严格模式双重渲染
在开发模式下,<React.StrictMode>
会让组件在初始化时执行两次渲染,以检测副作用。这在生产环境不会触发,只需要知道这是为了帮助开发调试即可。
3. 理解 Virtual DOM
虚拟 DOM(V-DOM)是 React 在内存中维护的一份轻量级 DOM 结构,能显著减少对真实 DOM 的频繁操作。
- 真实 DOM 操作昂贵
- 虚拟 DOM 先在内存中对比,再只更新有差异的地方
工作流程
- 生成初始虚拟 DOM
- 数据或 props 变动时,生成新的虚拟 DOM
- 对比新旧虚拟 DOM 的差别(Diff 过程)
- 有变化的地方才更新真实 DOM
这种按需更新机制提升了性能。比方说文本从 “Count: 0” 变成 “Count: 1”,React 只会修改文本内容,而不会重新创建整个 <h1>
标签。
4. Reconciliation:React 的高效更新算法
Reconciliation 是 React 用来高效处理 DOM 更新的过程,核心是 Diff 算法。
Diff 规则
- 不同类型的元素
如果type
变了(比如从<h1>
变<p>
,或从Card
组件变成List
组件),React 会销毁原节点并新建节点。
- 相同类型的元素
如果type
相同,只更新变更部分。例如修改属性或文本内容。
将 text 从 "Hello" 改为 "World" 会使 React 仅更新文本。
- 列表中的 Key
当使用map()
渲染列表时,务必给每个项加唯一key
,这样 React 才能跟踪列表项,做最小化更新。如果没有 key(或 key 不唯一),React 很可能重渲染整个列表,导致性能浪费。
代码错误(无key) → diff 效率低
如果在开始时添加了一个新项目,React 会重新渲染所有 <li>
元素,这样做很慢,因为 React 无法跟踪没有键的单个项目。
良好代码(key) → 优化对账
5. ReactJS 的性能优化技巧
5.1 React.memo():防止不必要的子组件重复渲染
在父组件刷新而子组件 props 未变的情况下,React.memo(Child)
能阻止子组件重复渲染。
只要 count
没变化,就不会重复渲染。
5.2 useMemo():缓存昂贵计算结果
如果某个函数计算量大且多次使用相同参数,可以用 useMemo
缓存结果,避免重复计算。
5.3 useCallback():缓存函数引用,减少子组件不必要的渲染
React 每次渲染都会重新创建函数。如果子组件接收函数作为 props,默认会认为 props 变了,进而触发子组件渲染。用 useCallback()
可以让函数在依赖不变时保持相同引用。
这样 ChildComponent
不会因为 onClick
prop 每次都换新函数而被动重渲染。
总结
ReactJS 的核心运行机制就是把 JSX 转成 React.createElement()
调用,再把这些 “React Element” 组成虚拟 DOM。通过比较新旧虚拟 DOM 的差异(Reconciliation),React 能用最小代价更新真实 DOM。基于这个原理,就能延伸出许多优化策略,比如:
- 使用
React.memo
防止子组件反复刷新 - 通过
useMemo
、useCallback
缓存耗时操作及函数引用 - 在列表中使用
key
,避免不必要的遍历和重绘
这些技巧能够在大规模项目中让性能和可维护性都大幅提升,也是真正掌握 ReactJS 的关键所在。