JSX 是一种语法,并不是 React 中的内容,时下接入 JSX 语法的框架越来越多,但与之缘分最深的仍然是 React。本节来讲一下 React 是如何摇身一变成为 DOM 的。

我们平时在写React时会用 JSX 来描述组件的内容,例如下面的代码中,render 方法 return 的内容就是 JSX 代码。

class App extends React.Component {
render() {
return (
<div className="App">
<h1 className="title">I am the title</h1>
<p className="content">I am the content</p>
</div>
);
}
}

我们考虑以下三个问题:

  1. JSX 的本质是什么,它和 JS 之间到底是什么关系?
  2. React 为什么要用 JSX?
  3. JSX 是如何映射为 DOM 的?

这一节我们就将这三个问题一一解答。

1)JSX 的本质是什么?它和JS之间的到底是什么关系?

JSX 到底是什么,我们先来看看 React 官网给出的一段定义:

JSX 是 JavaScript 的一种语法扩展,它和模板语言很接近,但是它充分具备 JavaScript 的能力。

“语法扩展”这一点在理解上几乎不会产生歧义,不过“它充分具备 JavaScript 的能力”这句,却总让人摸不着头脑,JSX 和 JS 怎么看也不像是一路人啊?这就引出了“JSX 语法是如何在 JavaScript 中生效的”这个问题。

JSX 是 JavaScript 的扩展,而不是 JavaScript 的某个版本,因此浏览器并不会天然支持,那么 JSX 是如何在 JavaScript 中生效的呢?

React 官网是这样的解释的:

JSX 会被编译为 React.createElement(), React.createElement() 将返回一个叫作“React Element”的 JS 对象。

那么 JSX 如何转换成 React.createElement() 的呢?答案就是通过 babel 转换。

我们直接打开 babel playground 来写一段 JSX 代码看一下 babel 转换后的结果。![image-20231204112041472](/Users/jiuyuezhang/Library/Application Support/typora-user-images/image-20231204112041472.png)

可以看到 JSX 代码都被转换成了 React.createElement 调用。

接下来我们总结一下来回答标题提到的两个问题。

JSX 是 JavaScript 的扩展,不是 JavaScipt 的某个版本,需要通过 Babel 进行转换成 JavaScript 代码。

JSX 会被 babel 转换为 React.CreateElement(...) 调用的形式,执行后返回的结果是一个对象。

2)React 为什么要用 JSX?

从上一节我们知道 JSX 等价于一次 React.createElement 调用,那么 React 官方为什么不直接引导我们用 React.createElement 来创建元素呢?

在实际功能效果一致的前提下,JSX 代码层次分明、嵌套关系清晰;而 React.createElement 代码则给人一种非常混乱的“杂糅感”,这样的代码不仅读起来不友好,写起来也费劲。

JSX 语法糖允许前端开发者使用我们最为熟悉的类 HTML 标签语法来创建虚拟 DOM,在降低学习成本的同时,也提升了研发效率与研发体验。

3)JSX 是如何映射为 DOM 的?

我们知道 JSX 经过babel转换后会变成 React.createElement(...) 的形式,接下来我们就来一起探讨一下 React.createElement(...) 是如何工作的?

3.1 入参解读:创造一个元素需要知道哪些信息

我们先来看看方法的入参:

export function createElement(type, config, children)

createElement 有 3 个入参,这 3 个入参囊括了 React 创建一个元素所需要知道的全部信息。

  • type:用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串,也可以是 React 组件类型或 React fragment 类型。
  • config:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。
  • children:子节点,如果有多个子节点,那么依次往后写。

举个例子:

<ul className="list">
<li key="1">1</li>
<li key="2">2</li>
</ul>

经过 Babel 转换后的形式为:

注意:从第三个入参开始往后,传入的参数都是 children

React.createElement("ul", {
// 传入属性键值对
className: "list"
}, React.createElement("li", {
key: "1"
}, "1"), React.createElement("li", {
key: "2"
}, "2"));

3.2 出参解读:初识虚拟DOM

下面的代码是 React.createElement(...) 调用的返回值格式。

注意:这是 fiber节点之前的每个节点的格式。

const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
$$typeof: REACT_ELEMENT_TYPE, // 内置属性赋值
type: type,
key: key,
ref: ref,
props: props, // 记录创造该元素的组件
_owner: owner,
}; //
if (__DEV__) {
// 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
} return element;
};

举个例子

const AppJSX = (<div className="App">

  <h1 className="title">I am the title</h1>

  <p className="content">I am the content</p>

</div>)

console.log(AppJSX)

输出为:

这个 ReactElement 对象实例,本质上是以 JavaScript 对象形式存在的对 DOM 的描述,也就是老生常谈的“虚拟 DOM”(准确地说,是虚拟 DOM 中的一个节点

既然是“虚拟 DOM”,那就意味着和渲染到页面上的真实 DOM 之间还有一些距离,这个“距离”,就是由大家喜闻乐见的ReactDOM.render方法来填补的。

在每一个 React 项目的入口文件中,都少不了对 React.render 函数的调用。下面我简单介绍下 ReactDOM.render 方法的入参规则:

复制代码

ReactDOM.render(
// 需要渲染的元素(ReactElement)
element,
// 元素挂载的目标容器(一个真实DOM)
container,
// 回调函数,可选参数,可以用来处理渲染结束后的逻辑
[callback]
)

ReactDOM.render 方法可以接收 3 个参数,其中第二个参数就是一个真实的 DOM 节点这个真实的 DOM 节点充当“容器”的角色,React 元素最终会被渲染到这个“容器”里面去。比如,示例中的 App 组件,它对应的 render 调用是这样的:

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

注意,这个真实 DOM 一定是确实存在的。比如,在 App 组件对应的 index.html 中,已经提前预置 了 id 为 root 的根节点:

<body>
<div id="root"></div>
</body>

JSX 代码是如何“摇身一变”成为 DOM 的?的更多相关文章

  1. 【React】在React中 JSX 代码如何转成 JS 代码?

    一.介绍 写 React 代码的朋友应该都是直接写 JSX 代码,JSX 让我们可以在 JS 中直接写 HTML 代码,可阅读性较高.本章节主要介绍 JSX 通过 babel 转换后会生成什么样式代码 ...

  2. 2.2.3 修改JSX代码

    /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { ...

  3. React系列文章:JSX生成真实DOM结点

    在上一篇文章中,我们介绍了Babel是如何将JSX代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了Babel编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const style ...

  4. React: JSX生成真实DOM结点

    在上一篇文章中,我们介绍了 Babel 是如何将 JSX 代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了 Babel 编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const ...

  5. React系列文章:Babel编译JSX生成代码

    上次我们总结了React代码构建后的Webpack模块组织关系,今天来介绍一下Babel编译JSX生成目标代码的一些规则,并且模拟整个生成的过程. 我们还是拿最简单的代码举例: import {gre ...

  6. 按“块”的方式写dom以及代码注释

    前言 首先这个文档中主要记述了自己在编写html代码时如何构建良好的dom结构的一些所思所想,在这一部分主要说明按“块”构建dom结构的思路.同时在这篇文档中也记述了自己对代码注释的理解,在这一部分主 ...

  7. 记录在苹果X手机上运行遇到的代码Dom被阻塞不更新的一个坑

    一.问题产生背景: 开发支付功能,代码逻辑如下:点击支付后,请求后台接口得到流水号以及第三方支付台链接,跳转支付台(在苹果手机则是弹出支付台层):支付完毕后返回支付页面,或中途退出支付台返回支付页面: ...

  8. json代码——json序列化,json-parse,JSONstringify格式化输出,虚拟DOM

    JS对象转为类似json的字符串,对象->字符串叫序列化,字符串->对象      是反序列化 ㈠json序列化 <script> var shy = new Object() ...

  9. React学习笔记-2-什么是jsx?如何使用jsx?

    什么是jsx?    JSX是JavaScript  XML 这两个单词的缩写,xml和html非常类似,简单来说可以把它理解成使用各种各样的标签,大家可以自行 百度.所以jsx就是在javascri ...

  10. React文档翻译系列(三)JSX简介

    # React文档翻译系列(三)JSX简介 先来看一下下面的变量声明: ``` const element = Hello world! ``` 这种有趣的标签语法既不是字符串也不是HTML. 这种形 ...

随机推荐

  1. vue3 + ElementPlus 封装函数式弹窗组件

    需求场景:弹窗组件需要支持自定义的插槽内容,删除的弹窗也要使用这个组件,只是样式不一样而已,希望在父组件使用删除弹窗的时候直接调用某个方法就可以显示弹窗 组件模拟 cuDialog 假设我的弹窗组件有 ...

  2. 查看Linux系统下CPU、内存、硬盘等信息

    做个记录,方便日后查看使用. 1. 查看CPU 1.1 查看CPU个数 [root@qy-ggyf-zyl-31 ~]# cat /proc/cpuinfo | grep "physical ...

  3. Unity UGUI的Image(图片)组件的介绍及使用

    UGUI的Image(图片)组件的介绍及使用 1. 什么是UGUI的Image(图片)组件? UGUI的Image(图片)组件是Unity引擎中的一种UI组件,用于显示2D图像.它提供了一种简单而灵活 ...

  4. 三维模型OSGB格式轻量化顶点压缩主要技术方法分析

    三维模型OSGB格式轻量化顶点压缩主要技术方法分析 在三维模型应用中,轻量化处理是提高数据传输效率.减少渲染时间和优化用户体验的重要手段.而OSGB格式是一种常见的三维模型格式,在进行轻量化处理时,顶 ...

  5. 论文日记五:QueryInst

    导读 实例分割领域21年的SOTA,整个模型结构设计总述为以下: 1)设计了类似于Cascade Mask R-CNN的多阶段迭代优化的bbox和mask预测头: 2)基于query的实例分割方法,延 ...

  6. 如何配置.h头文件include“”相对路径

    编译工程时,找的是当前main.c文件下的.h文件,如果当前路径下没有就会报错,当前路径用.\表示,上一级目录用..\表示. 如果你的main.h文件在main.c的上一级目录中Include 文件夹 ...

  7. Stable Diffusion

     Stable Diffusion  ...using diffusers Stable Diffusion is a text-to-image latent diffusion model cre ...

  8. 使用Java统计gitlab代码行数

    一.背景: 需要对当前公司所有的项目进行代码行数的统计 二. 可实现方式 1.脚本:通过git脚本将所有的项目拉下来并然后通过进行代码行数的统计 样例: echo 创建项目对应的文件夹 mkdir 项 ...

  9. 在 Rust 中实现 Repository 仓储模式

    前言 单位上有个 Rust 项目,orm 选型很长时间都没定下来,故先设计了抽象的仓储层方便写业务逻辑. 设计抽象接口 抽象只读接口,仅读取使用,目前需求仅用查询 id.查询全部和按名称搜索,当然理应 ...

  10. Shuffle 题解

    Shuffle 题目大意 给定一个长度为 \(n\) 的 01 序列 \(a\),你可以进行至多一次以下操作: 选定 \(a\) 的一个连续段,满足连续段内恰好有 \(k\) 个 \(1\),将该连续 ...