React 的底层工作原理
除了语法糖之外,React 的实际工作原理是什么?
这个问题困扰了我很久。出于好奇,我对 React 进行了深入研究,而我的发现也相当有趣。
我探索 React 的过程,最终成为了我做过的最令人大开眼界的事情之一。
所以,如果你也想了解 React 的底层工作原理,那么你来对地方了。
在本文中,我将分享一些关于 React 渲染过程的内部运作机制的有趣发现。
HTML、DOM 和 React 的本质
我们都知道网页是用HTML构建的。因此,网页本质上就是HTML文档。
可以通过名为 DOM 的 API 修改 HTML 文档,使用诸如querySelectorAll()等getElementsById()方法。
修改后,网页浏览器需要重新渲染页面以反映这些更改。
然而,这个过程对浏览器来说开销非常大。因此,如果你的页面会定期变化(也就是动态网页),那么直接操作 DOM 就效率低下。
事实上,重新绘制 DOM 比用 JavaScript 创建 10,000 个对象还要慢。
这一事实证明了 React 作为库的存在和实用性。
React 提供了一种声明式的方式来创建用户界面。换句话说,你无需具体指定网页的渲染方式或元素的插入方式。相反,你更需要关注的是要创建哪些元素,以及描述它们的外观和行为。
React元素本质上就是对象。
你可能知道也可能不知道,React 实际上就是一棵 JavaScript 对象树。
例如,考虑以下功能组件:
const Title = () => {
return (
<div>
<h1 id=”title”> Hello World </h1>
</div>
)
}
在 React 中,组件本质上是一个类或函数,它描述了你最终想要创建的 UI。
React 元素本质上是对 UI 的描述,最终会被插入到 DOM 中。因此,在render()调用 ReactDOM 库的方法之前,React 元素并不是 DOM 元素。
当从父组件调用组件时,React 将调用render()该子元素上的方法并返回一个 React 元素,这只是一个具有某些属性的普通对象。
例如,调用上述函数组件实际上会返回以下对象。
{
type: “div”,
key: null,
ref: null,
“$$typeof”: Symbol(react.element),
props: {
children: {
type: “h1”,
key: null,
ref: null,
props: {
id: “title”
children: “Hello World!”
},
}
}
}
该对象包含某些属性。
-
type:这是对所用标签类型的引用。如果使用的是内置的 DOM HTML 元素(例如 main、div 等),则 type 指向该 DOM 元素的字符串表示形式。但如果您使用标准
import ...语法导入自定义 React 组件,则您引用的是组件元素。 -
key:此属性用于使用唯一值在其他子元素中标识一个元素。这通常用于遍历子元素列表时。Ref
:对实际 DOM 节点的引用。 -
`typeOf`:此属性的值始终为符号(Symbol)。符号是 ES6 中引入的 JavaScript 数据类型。该对象接收一个值并返回一个唯一的符号。在 React 中,符号对象接收一个 `react.element` 对象。这是一种防止跨站脚本攻击的保护机制。该机制用于识别 React 元素,以避免恶意值被传递给 React 的情况。
-
props:包含所有元素的子元素。如果组件有多个子元素,则 children 属性将是一个数组而不是一个对象。每个子元素都具有相同的属性集。
现在,这个对象就是我们所说的虚拟 DOM。
构建这些对象的过程比直接写入 DOM 要高效得多。因此,我们可以创建一个虚拟 DOM,并对该对象树进行修改,而不是直接修改 DOM。
React 每次调用 render 函数时都会创建一个元素树。
和解
协调机制包含差异算法,该算法决定我们应该替换树的哪一部分。
换句话说,这就是 React 在进行更改时协调 DOM 树和 React 元素树的方式。
差异算法是一种能够区分两棵树并确定需要替换树的哪些部分的算法。
React 的一个重要特性是它如何响应顶级(根)元素的类型更改。
在这种情况下,React 会拆除整个元素树并构建一个新的元素树。
例如,如果div标签更改为另一个span标签,React 将销毁整个 DOM 树及其所有节点。同时,还会发生以下情况:
-
所有旧的组件实例(在
div)都将收到 componentWillUnmount 和等效的 useEffect 钩子。 -
span将以 作为根元素构建新的组件树。 -
React 将再次开始重新渲染。
-
新节点将被插入到DOM中。
-
新组件将接收 componentWillMont 和 componentDidMount,然后它们的 useEffects 钩子函数将运行。
-
旧的 props 和 state(针对 div)将被丢弃。
如果只有属性发生变化,那么 React 只会更新发生变化的属性,而不会销毁整个树。
假设我们正在像代码中那样从一个产品项切换到另一个产品项
<ProductList>
<Product id={5} /> // Product Five
</ProductList>
<ProductList>
<Product id={6} /> // Product Six
</ProductList>
React 会维护相同的组件实例,传入新的idprops,然后重新渲染,以便我们导航到不同的页面。
孩子们
假设我们有一个电影列表,我们在列表开头插入一部新电影。
<ul>
<li> First item <li>
<li> Second item <li>
</ul>
<ul>
<li> New First item <li>
<li> First item <li>
<li> Second item <li>
</ul>
React 无法知道更改发生在哪里。
因此,React 会拆除旧树并重建新树,但这并不高效。
相反,你应该为每个子节点的 key 属性传递一个唯一值。
React 会递归地检查键属性的唯一值并进行比较。这样,它就能知道应该将新项插入到列表的哪个位置。
<ul>
<li key=”first”> First item <li>
<li key=”second”> Second item <li>
<ul>
<ul>
<li key=”new-first”> New First item <li>
<li key=”first”> First item <li>
<li key=“second”> Second item <li>
</ul>
渲染到 DOM
import ReactDOM from 'react-dom'
import App from "./App.js";
ReactDOM.render(
<App />,
document.getElementById("root")
);
此过程会触发协调过程,该过程会构建 DOM 树、React 元素树以及整个差异比较过程。然后,React 最终会将 React 组件树插入到浏览器 DOM 中。
总结
我们已经看到,React Elements 本质上就是普通的对象。对于所有嵌套组件,React 会生成一个对象树,构成虚拟 DOM。
然后通过称为协调的过程对虚拟 DOM 进行更新。
文章来源:https://dev.to/ubahthebuilder/how-react-works-under-the-hood-1jbg附注:订阅我的新闻邮件,即可每周收到精选的网页开发文章。