对于那些初次接触 React 的开发者来说,它就像是一份“美味的鸡腿堡”,让人一试成主顾。React 以其原生的 JavaScript 语言、丰富的生态系统以及流畅的开发体验,吸引着越来越多的开发者。啊!这种感觉就像是在美漫中找到了英雄归宿一般令人兴奋。
虚拟DOM:提升性能的秘密武器
先记住React 框架采用的是 MVC 体系。而React的一大优势在于其虚拟DOM机制。如果你对虚拟dom不太了解,可以先看看这篇文章
虚拟DOM存在于内存中,允许React高效地计算并最小化实际DOM更新。每当状态改变时,React会比较新的虚拟DOM树与旧的版本,找出差异,然后只对实际DOM进行必要的更新。这种方式显著减少了浏览器重绘和回流的次数,相较原生JS频繁使用dom,从而提高了应用的性能。
这时候我们得回到JSX,先看一看在babel的编译下,理解JSX底层处理机制。使用babel-preset-react-app看这里
关于 JSX 底层处理机制
我们从 react 应用的入口开始对源码进行分析,建立下面代码
import React from 'react'
import ReactDOM from 'react-dom/client'
let x = 10
let y = 20
ReactDOM.createRoot(document.getElementById('root')).render(
<>
<h2 className="title">10.24早安</h2>
<div className="box">
<span>{x}</span>
<br />
<span>{y}</span>
</div>
</>
)
另外我在第一次学习 react 的时候,就有一个疑惑: import React from 'react' 这段代码中,React 似乎在代码中没有任何地方被用到,为什么要引入呢?
先解决为啥React要引入
即使看起来 React 没有直接在代码中被使用,引入它仍然是必要的。这是因为 React 的 JSX 语法实际上是由 Babel(或其他 JSX 转换器)转换为普通的 JavaScript 代码
// JSX
<div className="example" />
// 转换后
React.createElement("div", { className: "example" });
JSX 看起来像是 XML 或 HTML,但实际上它是 JavaScript 的一种语法糖。当你编写 JSX 时,例如上面 <div />,Babel 会将其转换为 React.createElement('div', null) 的调用
如果你不导入 React,当代码被编译时,没有在作用域找到,React.createElement 将无法解析,导致运行时错误
同样,对于类组件的 Component 继承
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
return <div>Hello, world!</div>;
} }
{ Component } 是从 react 库中解构出来的,创建MyComponent类组件
后面,react团队考虑系统兼容,React18 引入React Hook 。(React Hook 和类组件是两种不同的构建和管理插件的方式,看看后面再出篇文章讲吧)
很多情况下你可以不需要显式地导入 React,因为函数式组件不需要继承 Component,而且许多钩子(如 useState, useEffect)可以直接从 react 中解构出来。然而,如果你使用了 JSX,React 仍然需要被导入,除非你配置了 Babel 插件(如 @babel/preset-react)以启用自动引入 React 的功能(通过设置 runtime: 'automatic' 选项)
回来看babel编译JSX
使用babel 官网try it out进行编译,网址
第一步:把我们编写的 JSX 语法,编译为虚拟 DOM 对象「virtualDOM」
虚拟 DOM 对象是框架自己内部构建的一套对象体系(对象的相关成员都是 React 内部规定的),基于这些属性描述出我们所构建视图中的 DOM 节点的相关特征!
-
基于
babel-preset-react-app把 JSX 编译为React.createElement(...)这种格式!-
只要是元素节点,必然会基于
createElement进行处理! -
React.createElement(ele, props, ...children)ele: 元素标签名「或组件」props: 元素的属性集合(对象)「如果没有设置过任何的属性,则此值是 null」children: 第三个及以后的参数,都是当前元素的子节点
-
-
再把
createElement方法执行,创建出 virtualDOM 虚拟 DOM 对象「也有称之为:JSX 元素、JSX 对象、ReactChild 对象...」!!
virtualDOM = {
$$typeof: Symbol(react.element),
ref: null,
key: null,
type: 标签名「或组件」,
// 存储了元素的相关属性 && 子节点信息
props: {
元素的相关属性,
children: 子节点信息「没有子节点则没有这个属性、属性值可能是一个值、也可能是一个数组」
}
}
第二步:把构建的 virtualDOM 渲染为真实 DOM
- 真实 DOM:浏览器页面中,最后渲染出来,让用户看见的 DOM 元素!!
补充说明:第一次渲染页面是直接从 virtualDOM -> 真实 DOM;但是后期视图更新的时候,需要经过一个 DOM-DIFF 的对比,计算出补丁包 PATCH(两次视图差异的部分),把 PATCH 补丁包进行渲染!!
babel手写实操
createElement这个函数是 React 中用于创建虚拟 DOM 元素的核心函数之一,它模仿了 React 内部的 React.createElement 方法。
// 导出默认函数 createElement,用于创建虚拟 DOM 元素
export default function createElement(ele, props, ...children) {
// 创建一个虚拟 DOM 对象,包含必要的属性
let virtualDOM = {
$$typeof: Symbol.for('react.element'), // 用于标识这是一个 React 元素对象
key: null, // 元素的唯一标识符,用于优化更新操作
ref: null, // 用于获取对 DOM 节点或组件实例的引用
type: null, // 元素类型(可以是标签名、函数组件、类组件等)
props: {} // 其他,元素的属性和子元素
};
// 设置虚拟 DOM 的类型为传入的 ele 参数
virtualDOM.type = ele;
// 如果 props 不为空,则使用展开运算符,展开到 virtualDOM.props 中
if (props != null) {
virtualDOM.props = { ...props };
}
// 处理子元素 children
const len = children.length;
if (len === 1) {
// 如果只有一个子元素,直接将其赋值给 virtualDOM.props.children
virtualDOM.props.children = children[0];
} else if (len > 1) {
// 如果有多个子元素,将它们作为‘一个数组 ’赋值给 virtualDOM.props.children
virtualDOM.props.children = children;
}
// 返回创建的虚拟 DOM 对象
return virtualDOM;
}
注意事项
$$typeof属性:这是 React 内部用于标识虚拟 DOM 对象的一个特殊属性。它使用Symbol.for('react.element')来确保每个虚拟 DOM 对象都有一个唯一的标识符。这对于 React 的内部机制非常重要,因为它可以帮助 React 区分不同的对象类型。key和ref属性:虽然在这段代码中key和ref被初始化为null,但在实际应用中,你可以根据需要为它们赋值。key通常用于列表渲染中的唯一标识符,而 ref不仅仅是创建响应式ref工具,还 用于获取对 DOM 节点或组件实例的引用。- 子元素处理:这段代码处理了两种情况:单个子元素和多个子元素。如果你希望更严格地处理子元素,可以考虑添加更多的逻辑来处理
undefined或null的情况,或者确保子元素的类型是有效的(例如,过滤掉非 React 元素)。 - 扩展属性:使用
...props来扩展属性对象是一种常见的做法,但它可能会覆盖虚拟 DOM 对象中已有的属性。如果你需要保留某些特定的属性(如children),可以在扩展之前进行检查或合并。
通过这种方式,createElement 函数可以创建一个完整的虚拟 DOM 对象,供 React 渲染引擎使用。这个虚拟 DOM 对象描述了如何在实际的 DOM 中创建和更新元素,从而实现了高效的 UI 更新。
我相信看到这里的小伙伴中肯定会出现绝世高手~
希望这篇文章对大家有帮助,欢迎评论区探讨学习,学会的话也还请给本文一个点赞支持哦~