理解 React 中的渲染 ♻ ⚛️
嘿!👋🏼
我是 Mateo Garcia,我是哥伦比亚麦德林 React Medellin 社区的联合组织者。今年我开始了名为“和 Mateo 一起编码”的系列文章;我的第一篇文章是关于编写 React 组件的 6 种方法。
今天我想和大家分享一下我这几周对 React 渲染机制的研究。首先,我要说的是,React 中的渲染概念与我们理解的略有不同。让我们来探究一下其中的原因。
目录
1.简介
2. VirtualDOM
3.渲染
4.协调
5.提交
6.示例
介绍
您之所以来到这里,可能是因为您使用过 React,使用过它的 API,修改过组件的状态,并见证过各种神奇的体验。然而,有时更详细地了解一下 React 的工作原理也会有所帮助。当 React 应用程序持续重新渲染、应用程序规模大幅扩大以及组件复杂且性能开销巨大时,性能问题就可能出现。因此,Understanding rendering in React
所有使用 React 库的人都应该了解这一点。
要理解 React 为何如此之快,了解以下四个概念非常重要:
- 虚拟 DOM。
- 使成为。
- 和解。
- 犯罪。
让我们开始吧
虚拟DOM
虚拟 DOM 是一种策略,旨在解决使用 Web 或移动应用程序时 DOM 所遭受的修改或突变问题。随着应用程序变得越来越复杂,渲染整个文档树的成本过高;突变指的是 DOM 可能发生的任何变化:插入/修改/删除元素或其属性。
因此,虚拟 DOM 就用来表示内存中的 DOM 树。它使用 state 和 props 进行计算,最终决定实际 DOM(我指的是浏览器 DOM,哈哈)中的哪些元素需要修改。摘自React 官方网站:
虚拟 DOM (VDOM) 是一种编程概念,其中 UI 的理想或“虚拟”表示保存在内存中,并通过诸如 ReactDOM 之类的库与“真实” DOM 同步。此过程称为协调。
首先,我说过,React 与我们通常理解的渲染概念有所不同,我个人认为渲染是同步 DOM 变化的过程。React 通过三个步骤来同步 DOM 的变化。
使成为
渲染是一个由应用程序某些组件的状态变化触发的过程,当发生状态变化时,React:
- 它将从应用程序的根目录收集所有由于其状态或道具发生变化而请求重新渲染的组件。
- 它将调用这些组件
- 如果您使用
function components
它将调用函数本身,类似于Header(props)
。 - 如果您使用
class components
它将调用YourComponent.render()
。
- 如果您使用
即使该过程的名称是渲染,此时,DOM 还没有被修改或改变,如果你像我一样思考渲染的含义,这可能会有点棘手。
由于我们通常使用JSX
,代码将被转换为React.createElement(...)
。 的输出createElement
将描述应用程序在下一个渲染阶段(称为:
和解
一旦重新渲染发生,React 就会具有两个版本的输出上下文React.createElement
,即状态改变之前执行的版本和状态改变之后执行的版本。
此时两个对象正在描述 UI,React 通过 O(n^3) 阶的启发式算法将能够确定哪些元素需要再次表示。
在技术细节方面,React 团队向我们透露了 React 如何识别哪些元素受到影响的一些方面:
-
必须重新创建改变类型的元素。
-
元素属性内的更改将被替换,而无需卸载该元素。
-
元素子元素内的升级会重新创建所有子元素
-
用作属性的子元素内的更新
key
会被比较,并且仅显示新项目。
犯罪
React 计算出应用程序树中所有需要进行的更改后,react-dom
就会针对浏览器和react-native
移动平台,对浏览器或移动应用程序 API 进行修改(终于!🥳)。React 会同步清理之前的布局效果,运行新的布局效果,然后浏览器会绘制 DOM,之后,React 会清理之前的布局效果并挂载新的效果;我所说的效果指的是生命周期方法,例如useLayoutEffect和useEffect。
为了更深入地解释生命周期方法部分,我带来了 Donavon West 和他的贡献者们创建的这张精彩图表。这是项目仓库,快来看看吧!
在继续示例之前,需要先理解的是,在正常情况下,如果一个组件调用render
,它将自动为其所有子组件执行此操作。但是,在某些特殊情况下,可以阻止某些组件重新渲染,我计划讨论这个问题,不过,您可以阅读React.PureComponent
、React.memo
、React.useMemo
和React.useCallback
。
例子
这是代码
import * as React from "react";
import { useRenderTimes } from '../../utils';
function getRandomHEX() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
function Header() {
const [color, setColor] = React.useState("#111");
const count = useRenderTimes();
return (
<header style={{ backgroundColor: color }}>
<p>Header component has re-rendered {count} times</p>
<button onClick={() => setColor(getRandomHEX())}>Click</button>
</header>
);
}
function Footer() {
const count = useRenderTimes();
return (
<footer>
<p>Footer component has re-rendered {count} times</p>
</footer>
);
}
function App() {
const count = useRenderTimes();
return (
<>
<Header />
<main>
<p>Hey, nice to see you again 👋🏼</p>
<p>The App component has re-rendered {count} times</p>
</main>
<Footer />
</>
);
}
export { App };
}
useRenderTimes
这是一个钩子,可以让我们累计组件重新渲染的次数。我在Kent C Dodds 的一篇文章中看到了它,非常感谢!
import * as React from 'react';
function useRenderTimes() {
const renderRef = React.useRef(0);
React.useEffect(() => {
renderRef.current = renderRef.current + 1;
});
return renderRef.current;
}
export { useRenderTimes };
该<Header />
组件有自己的状态,一旦我们开始按下按钮,它就会改变。我们来看看
刚刚发生的事情是:
- 组件中的事件
<Header />
触发了状态更改。已安排渲染。 - VirtualDOM 开始分析哪些组件被标记为需要重新渲染。只
<Header />
需要重新渲染即可。 - 通过协调步骤,确定了的风格
<header></header>
正在发生变化。 - 向 DOM 发送提交。
- 轰隆隆,我们看到背景颜色发生了变化。
最后的想法
尽管 React 中的渲染过程可能很复杂,但我们必须承认整个 React 团队为提升 Web 开发日常体验所做的出色工作。了解一个工具的深层原理,对于刚开始接触它的人,以及长期使用它并希望了解其背后机制的人来说,都很有帮助。
我要感谢那些不断努力以最通俗易懂的方式分享所有知识的专家,其中包括Mark Erikson和Kent C Dodds。我留下了他们博客的链接。每篇文章都是一座金矿,值得我们去发现。
如果您觉得这篇文章有用,并且希望看到更多内容,欢迎您对此做出回应,我会非常高兴。如果您有任何意见或修改意见,可以帮助改进这篇文章,我将非常乐意收到。感谢您抽出时间👋🏼💙。
文章来源:https://dev.to/mateo_garcia/understanding-rendering-in-react-i5i