您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

实现JSX的转换

时间:2023-03-20 17:09:11  来源:微信公众号  作者:前端时光屋
当我们在项目中使用 React 构建界面时,主要使用的就是 React 包。它提供了开发者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大多数 React 项目的基础。

 

 

前言

由于近期在看React框架源码、底层实现方面的知识,所以想把学习心得整理出来。

这也是一个新的系列「从0实现React 18核心模块」的第一篇。

接下来还会更新:render、commit阶段的实现,以及Hooks架构、useState、useEffect、单双节点Diff的过程还有React 18中的并发更新原理。

在看文章之前,我们可以先想几个问题:

  • JSX 是什么语法?
  • JSX 有什么优势,它的转换规则是什么或者它内部是如何实现的?
  • 既然 React 一直在使用 JSX,那它的实现被写应该写在哪个包里(比如react、react-dom,react-reconciler)?
  • 在React 17之前和React 17之后,JSX转换的方法实现有哪些异同?
  • 如何实现React.createElement方法和运行时的 jsx 方法?
  • 写一个Demo引入自己实现的jsx方法,看看运行结果

下文提到的 big-react 是从0到1实现的React的核心功能模块原理的项目

如果自己实现一个 React 框架,它需要包含哪些内置的包:

  • react包是 React 的核心库,提供了创建和管理组件所需的基本功能(比如组件创建、组件生命周期管理、虚拟DOM以及Hooks等),主要是一些和宿主环境无关的方法。
  • react-reconciler包实现了 React 的 reconciliation 协调算法,是一种核心优化策略的实现,主要自定义协调器的实现。以及在不同的平台或环境中使用 React。
  • shared包是big-react公用的辅助方法,和宿主环境无关。

如果还有一个必要的包,那就是react-dom:

  • react-dom:这个包提供了将 React 与 DOM(浏览器环境)集成的方法。它包含了用于将 React 组件渲染到 DOM 中的 ReactDOM.render() 函数,以及其他与浏览器环境相关的实用功能。对于在浏览器中运行的 React 应用程序,react-dom 是必需的。

react与react-reconciler包是什么

react包为我们提供了什么

当我们在项目中使用 React 构建界面时,主要使用的就是 react​ 包。它提供了开发者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大多数 React 项目的基础。

react-reconciler包实现了什么​

react-reconciler包是一个更底层、更高级的库,它实现了reconciliation协调算法,reconciliation是 React 的一种核心优化策略,用于在更新组件时比较虚拟DOM树的差异,并将实际更改应用到实际的DOM树。这有助于提高性能,因为避免了不必要的DOM操作。

它主要用于创建自定义渲染器,以及在不同的平台中去使用 React。例如,react-dom(用于Web平台)和react-native(用于移动应用)都使用react-reconciler作为底层库,实现了针对各自平台的渲染逻辑。

JSX 是什么

const element = <div className="contAIner">Hello, world!</div>;

在React中,JSX是一种JAVAScript语法扩展,允许你在JavaScript代码中编写类似html的标记。要使用JSX,需要在构建过程中将其转换为标准的JavaScript代码。

通常,这个转换过程包括两个主要部分:

  • 编译时:通常指将 JSX 语法转换为浏览器可以理解的普通 JavaScript 代码的过程,这个过程通常由 Babel 完成。
  • 构建时:在将JSX语法转换为标准的JavaScript代码后,通常会使用构建和打包工具(如Webpack、Rollup)对代码进行优化、压缩和打包。打包工具将源代码和依赖项组合成一个或多个文件(“bundles”或“chunks”),用于在浏览器中运行。
  • 运行时:React会根据编译后的代码创建虚拟DOM树,然后将其渲染到实际的DOM中。还会发生的阶段有状态管理和更新、事件处理和Diff算法的比较等。

JSX 被 Babel 编译成了什么

在React 17之前,JSX语法会被编译成React.createElement函数的调用,用来创建虚拟DOM元素。

转换结果如下:

const element = React.createElement(
  "div",
  { className: "container" },
  "Hello, world!"
);

从React 17开始,引入了新的JSX转换功能,称为"Runtime Automatic"(自动运行时)。这意味着在使用JSX语法时,不再需要手动引入React库。在自动运行时模式下,JSX会被转换成新的入口函数,import {jsx as _jsx} from 'react/jsx-runtime'; 和 import {jsxs as _jsxs} from 'react/jsx-runtime';。

转换结果如下:

import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", { 
  className: "container", 
  children: "Hello, world!" 
});

接下来我们就来实现jsx方法或React.createElement方法(包括dev、prod两个环境)。

工作量包括:

  • 实现jsx方法
  • 实现打包流程
  • 实现调试打包结果的环境

实现 jsx 转换方法

jsx 转换方法包括:

  • React.createElement方法
  • jsxDEV方法(dev环境)
  • jsx方法(prod环境)

实现React.createElement

在React 17之前,JSX转换应用的是createElement方法,下面是它的实现:

/**
 * 
 * @param type 元素类型
 * @param config 元素属性,包括key,不包括子元素children
 * @param maybeChildren 子元素children
 * @returns 返回一个ReactElement
 */
const createElement = (
  type: ElementType, 
  config: any, 
  ...maybeChildren: any
) => {
  // reactElement 自身的属性
  let key: Key = null;
  let ref: Ref = null;

  // 创建一个空对象props,用于存储属性
  const props: Props = {};

  // 遍历config对象,将ref、key这些ReactElement内部使用的属性提取出来,不应该被传递下去
  for (const prop in config) {
    const val = config[prop];
    if (prop === 'key') {
      if (val !== undefined) {
        key = '' + val;
      }
      continue;
    }
    if (prop === 'ref') {
      if (val !== undefined) {
        ref = val;
      }
      continue;
    }
    // 去除config原型链上的属性,只要自身
    // 一般使用{...props}将所有属性都传递下去,所以摘除ref、key属性外需要被保存到props中
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val;
    }
  }

  const maybeChildrenLength = maybeChildren.length;
  if (maybeChildrenLength) {
    // [child] [child, child, child]
    if (maybeChildrenLength === 1) {
      props.children = maybeChildren[0];
    } else {
      props.children = maybeChildren;
    }
  }

  return ReactElement(type, key, ref, props);
};

注意:React.createElement方法和jsx方法的区别这里只体现在第三个参数上。

实现jsx方法

从React 17之后,JSX转换应用的是jsx方法,下面是它的实现:

/**
 * 
 * @param type 元素类型
 * @param config 元素属性
 * @param maybeKey 可能的key值
 * @returns 返回一个ReactElement
 */
const jsx = (type: ElementType, config: any, maybeKey: any) => {
  // 初始化key和ref为空
  let key = null;
  let ref = null;

  // 创建一个空对象props,用于存储属性
  const props: Props = {};

  // 遍历config对象,将ref、key这些ReactElement内部使用的属性提取出来,不应该被传递下去
  for (const prop in config) {
    const val = config[prop];
    if (prop === "key") {
      continue;
    }
    if (prop === "ref") {
      if (val !== undefined) {
        ref = val;
      }
      continue;
    }
    // 一般使用{...props}将所有属性都传递下去,所以摘除ref、key属性外需要被保存到props中
    if ({}.hasOwnProperty.call(config, prop)) {
      props[prop] = val;
    }
  }

  // 将 maybeKey 添加到 key 中
  if (maybeKey !== undefined) {
    key = "" + maybeKey;
  }

  return ReactElement(type, key, ref, props);
};

这段代码定义了一个jsx函数,主要用于创建React元素。首先,它会提取可能存在的key和ref属性,并将剩余属性添加到一个新的props对象中。最后用ReactElement函数创建一个React元素并返回。

从上面代码中可以看到还实现了ReactElement方法:

// jsx-runtime.js
const supportSymbol = typeof Symbol === 'function' && Symbol.for;

// 为了不滥用 React.elemen,所以为它创建一个单独的键
// 为React.element元素创建一个 symbol 并放入到 symbol 注册表中
export const REACT_ELEMENT_TYPE = supportSymbol
  ? Symbol.for('react.element')
  : 0xeac7;

export const ReactElement = function (type, key, ref, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
    _mark: 'lsh',
  };
  return element;
};

export const jsx =...

用自己实现的的jsx接入Demo

我们试着把自己实现的jsx方法,创建一个ReactElement,看它是否能够渲染在页面上。

图片

实现jsx方法

jsx-Demo运行地址

jsx方法和createElement的区别

jsx函数和createElement函数都用于在React中创建虚拟DOM元素,但它们的语法和用法有所不同。jsx函数来自于React 17及更高版本中的新的JSX转换功能,称为"Runtime Automatic"。

以下是两者之间的主要区别:

  1. 语法和转换方式:jsx函数用于处理新的JSX转换方式,其语法更简洁。createElement函数用于处理传统的JSX转换方式。

例如,一个JSX元素:

const element = <div className="container">Hello, world!</div>;

使用createElement转换后的代码如下:

const element = React.createElement(
  "div",
  { className: "container" },
  "Hello, world!"
);

使用jsx函数(自动运行时)转换后的代码如下:

import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", { className: "container", children: "Hello, world!" });
  1. ​子元素和key值处理:jsx函数将子元素作为属性(children)传递,而createElement函数将子元素作为额外的参数传递。同时子元素上的key值在jsx函数中也会以第三个参数的形式传递,而在createElement函数中,则是存在于config第二个参数中。

在createElement函数中:

React.createElement("div", {className: "App", key: "appKey"}, "hello,app");

在jsx函数中:

import { jsx as _jsx } from "react/jsx-runtime";

_jsx("div", {className: "app", children: "hello,app"}, "appKey");
  1. ​兼容性和版本:createElement函数在所有React版本中可用,而jsx函数仅在React 17及更高版本中提供。尽管React团队推荐使用新的JSX转换方式,但许多现有项目可能仍在使用createElement函数。

这时可能产生两个疑问:

  • 从React 17之后使用Runtime Automatic自动运行时有什么好处?
  1. 简化组件代码:不再需要在每个组件文件顶部添加**import React from 'react';**。这使得组件代码更简洁,更易于阅读和维护。
  2. 节省包大小:由于不再需要导入整个React对象,构建工具可以更好地优化输出代码,从而减小输出包的大小。
  • 改成jsx函数后,为什么要把key属性单独拿出来放在第三个参数?

在之前的React版本中,每当创建一个新的React元素时,React都需要从属性对象中提取key​和ref,这会导致额外的性能开销。

将key​作为单独的参数传递,可以让React在处理虚拟DOM树时更容易地访问key,无需每次都从属性对象中查找。这有助于提高React的性能和效率,特别是在处理大量元素和复杂组件树时。

实现打包流程

打包流程稍微有些复杂,后续写到文章里。

简单来说就是使用 Rollup,将编写jsx方法的文件打包出来,通过pnpm link --global的方式生成一个全局的react包,这样就可以通过pnpm link react --global调试自己创建的 create-react-app demo项目了。

图片

构建react包思路



Tags:JSX   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
你了解 JSX,那你了解 StyleX 么?
大家好,我卡颂。近日,Meta开源了一款「CSS-in-JS库」 &mdash;&mdash; StyleX。看命名方式,Style - X是不是有点像JS - X,他们有关系么?当然有。JSX是一种「用JS描述HTML」的语法...【详细内容】
2023-12-08  Search: JSX  点击:(159)  评论:(0)  加入收藏
JSX是Vue前端开发的未来吗?
在前端开发中,Vue 一直以其简单、高效的框架而备受开发者青睐。然而,随着 React 在市场上的流行,许多开发者开始对 JSX(JavaScript XML)这种声明式编程风格产生兴趣。本文将探讨...【详细内容】
2023-08-16  Search: JSX  点击:(257)  评论:(0)  加入收藏
实现JSX的转换
当我们在项目中使用 React 构建界面时,主要使用的就是 React 包。它提供了开发者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大...【详细内容】
2023-03-20  Search: JSX  点击:(130)  评论:(0)  加入收藏
简单理解JavaScript,TypeScript和JSX
JavaScript:基本概念:JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于浏览...【详细内容】
2019-06-18  Search: JSX  点击:(1201)  评论:(0)  加入收藏
▌简易百科推荐
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(0)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(6)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
站内最新
站内热门
站内头条