前言
最近我在阅读React的官方文档时,对React的渲染和提交机制有了更深入的理解。作为前端开发者,我们每天都在写组件,但很少思考这些组件是如何最终变成屏幕上可见的UI的。本文将分享我对React渲染机制的理解,希望能帮助大家更好地掌握React的核心工作原理。
什么是渲染
渲染就是将我们编写的React组件转换为最终显示在屏幕上的内容。我们平时开发时,写完代码启动项目往往就能看到效果,往往忽略了React在背后完成的复杂工作。
举个例子,我们编写一个简单的登录表单组件:
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
return (
<form>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">登录</button>
</form>
);
}
这个组件需要经过React的渲染流程,才能最终在页面上显示为可交互的表单。整个过程对开发者是透明的,但理解其中的机制对性能优化和问题排查至关重要。
渲染的过程
React的渲染过程可以分为以下几个关键步骤:
一、触发渲染
有两种主要方式会触发渲染:
1. 初始化触发渲染(初次渲染)
当应用启动时,会触发初次渲染。框架和沙箱有时会隐藏这部分代码,但它的底层是通过调用 createRoot 方法并传入目标 DOM 节点,然后用你的组件调用 render 函数完成的:
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
// 初次渲染的过程
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
export default function Image() {
return (
<img
src="https://i.imgur.com/ZF6s192.jpg"
alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
/>
);
}
2. 状态变化触发渲染(重渲染)
自身状态变化:当组件内部的状态(state)更新时
const [count, setCount] = useState(0);
// 调用setCount会触发重新渲染
setCount(count + 1);
父组件状态变化:父组件重新渲染会导致所有子组件重新渲染(即使子组件的props没有变化)
二、虚拟DOM生成
React会执行组件函数,生成虚拟DOM树。虚拟DOM是轻量级的JavaScript对象,描述UI应该是什么样子:
// JSX会被编译为React.createElement调用
<div className="title">Hello</div>
// 转换为虚拟DOM对象
{
type: 'div',
props: { className: 'title', children: 'Hello' }
}
三、协调
React 将新生成的虚拟 DOM 与上一次渲染的虚拟 DOM 进行对比(Diffing 算法),找出需要更新的部分:
- 差异计算:识别哪些节点需要添加、修改或删除。
- 优化策略:React 使用高效的算法(如基于
key的列表对比)最小化操作。
四、提交到真实DOM
在渲染(调用)你的组件之后,React 将会修改 DOM。
对于初次渲染,React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
对于重渲染,React 将应用最少的必要操作(在渲染时计算!),以使得 DOM 与最新的渲染输出相互匹配。
五、浏览器渲染(用户看到页面)
浏览器处理DOM更新,计算样式(CSSOM)、布局(Layout)、绘制(Paint),最终显示在屏幕上。
需要注意
上面过程中需要注意几点
渲染 ≠ 直接更新真实 DOM
React的渲染过程实际上分为"渲染阶段"(生成虚拟DOM)和"提交阶段"(更新真实DOM)。组件可能在渲染阶段被执行多次,但只有最终结果会被提交到DOM,整个过程是按步骤的,而不是直接更新正式DOM
什么是虚拟DOM
虚拟DOM(Virtual DOM)是React的核心概念之一,它是一个轻量级的JavaScript对象,用于描述真实DOM(文档对象模型)的结构和状态。虚拟DOM并不是真实的浏览器DOM元素,而是React在内存中构建的DOM的抽象表示。
什么是真实DOM
真实DOM 元素(Document Object Model Element) 是浏览器对 HTML 文档中所有标签(如 <div>、<p>、<button> 等)的抽象表示。他既包含了HTML标签结构,也包含随之关联的CSS样式和JavaScript行为。
React 通过 虚拟 DOM(Virtual DOM) 间接操作真实 DOM,优化性能:
- 虚拟 DOM:React 组件返回的 JSX 会转换为轻量级的 JavaScript 对象(虚拟 DOM)。
- 对比更新:当状态变化时,React 生成新的虚拟 DOM,与旧的对比(Diffing),找出最小变化。
- 更新真实 DOM:仅将变化的部分应用到真实 DOM 上。
为什么需要这样子做?
在传统的前端开发中,直接操作真实DOM存在几个关键问题:
- DOM操作成本高昂:每次DOM更新都会触发浏览器的重排(Reflow)和重绘(Repaint),频繁操作会导致性能下降
- 手动优化困难:开发者需要自己跟踪哪些DOM需要更新,容易出错
- 跨平台兼容性问题:不同浏览器对DOM的实现有差异
Diffing算法是如何高效更新的?
React的Diffing算法遵循以下原则:
- 同层比较:只比较同一层级的节点
- 类型不同则重建:如果组件类型改变,直接重建整个子树
- key的重要性:列表项使用稳定的key可以提高比较效率
// 不好的做法:使用index作为key
{todos.map((todo, index) => (
<Todo key={index} {...todo} />
))}
// 好的做法:使用唯一id作为key
{todos.map((todo) => (
<Todo key={todo.id} {...todo} />
)}
如何优化React渲染性能
-
避免不必要的渲染
- 使用
React.memo缓存函数组件 - 类组件继承
PureComponent - 合理使用
shouldComponentUpdate
- 使用
-
优化状态更新
- 合并多个
setState调用 - 使用
useMemo缓存计算结果 - 使用
useCallback缓存函数引用
- 合并多个
-
代码分割
- 使用
React.lazy动态加载组件 - 配合
Suspense提供加载状态
- 使用
-
列表优化
- 为列表项设置唯一的
key - 考虑使用虚拟滚动技术处理长列表
- 为列表项设置唯一的
-
合理使用Context
- 将Context的值拆分为多个Context
- 避免在Context中放置频繁变化的值
结语
理解React的渲染机制是成为高级React开发者的关键一步。通过掌握虚拟DOM、协调过程和Diffing算法的工作原理,我们可以编写出性能更好的React应用。希望本文能帮助你更深入地理解React的渲染机制,在实际开发中做出更明智的架构和优化决策。
如果你对React渲染机制有任何问题或见解,欢迎在评论区留言讨论!