JSX 代码是如何“摇身一变”成为 DOM 的?

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

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

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:子节点,如果有多个子节点,那么依次往后写。

举个例子:

jsx 复制代码
<ul className="list">
  <li key="1">1</li>
  <li key="2">2</li>
</ul>

经过 Babel 转换后的形式为:

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

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

3.2 出参解读:初识虚拟DOM

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

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

react 复制代码
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;
};

举个例子

jsx 复制代码
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 方法的入参规则:

复制代码

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

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

jsx 复制代码
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

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

jsx 复制代码
<body>
    <div id="root"></div>
</body>
相关推荐
aiguangyuan2 小时前
浅谈 React Hooks
react·前端开发
whatever who cares2 天前
React hook之userReducer
react.js·react
aiguangyuan2 天前
React Hooks 基础指南
react·前端开发
aiguangyuan3 天前
React 项目初始化与搭建指南
react·前端开发
aiguangyuan3 天前
React 组件异常捕获机制详解
react·前端开发
aiguangyuan3 天前
深入理解 JSX:React 的核心语法
react·前端开发
aiguangyuan4 天前
React 基础语法
react·前端开发
aiguangyuan5 天前
React 核心概念与生态系统
react·前端开发
aiguangyuan5 天前
React 18 生命周期详解与并发模式下的变化
react·前端开发
linweidong13 天前
汇量科技前端面试题及参考答案
webpack·vue3·react·前端面试·hooks·懒加载·flex布局