有了 React.createElement 为什么还需要 JSX runtime,作用是什么?

简介: 之前的一篇 基于 Webpack 从 0 到 1 启动一个 React 项目 文章中有介绍的是如何从 0 到 1 配置 React 项目中的 JSX 转换,在查阅文档时有介绍到从本质,JSX 只是为

之前的一篇 基于 Webpack 从 0 到 1 启动一个 React 项目 文章中有介绍的是如何从 01 配置 React 项目中的 JSX 转换,在查阅文档时有介绍到

从本质上讲,JSX 只是为  React.createElement(component, props, ...children) 函数提供的语法糖

但真正在浏览器查看编译结果时发现 JSX 没有完全和 React.createElement(component, props, ...children) 画等号,实际上编译结果有可能是两个

  1. React.createElement(component, props, ...children)
  2. JSX runtime

您可以在线查看完整的示例源代码

区别

首先从示例里面找到这两个编译结果,分别如下

// React.createElement
return /*#__PURE__*/ React.createElement(
  "div",
  null,
  "count:" + count,
  /*#__PURE__*/ React.createElement(
    "button",
    {
      onClick: handleClick,
    },
    "click + 1"
  )
);
// JSX runtime
return /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsxs)(
  "div",
  {
    children: [
      "count:" + count,
      /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsx)(
        "button",
        {
          onClick: handleClick,
          children: "click + 1",
        }
      ),
    ],
  }
);

为了便于观看,我做了格式化处理,实际编译结果应该只有一行

阅读过后会发现只有渲染函数上有区别((0, fn)()IFFE),但是根据 React 文档上说 JSX 实际上是 React.createElement 的语法糖,那为什么还需要 react_jsx_runtime__WEBPACK_IMPORTED_MODULE_1__.jsx(其实就是 JSX runtime

JSX runtime

其实对于这个问题,React 文档专门有一个博客文章来介绍这个部分,JSX runtime 是一种新的 JSX 转换,像过去做 JSX 转换的时候,比如使用 @babel/preset-react 去转,默认是将 JSX 编译成 React.createElement 的形式,而现在你使用通过配置以后再转就是 JSX runtime 的形式,具体配置如下

// .babelrc
// React.createElement
{
  "presets": [
    ["@babel/preset-react", {
      "runtime": "classic"
    }]
  ]
}
// .babelrc
// JSX runtime
{
  "presets": [
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ]
}

对于 React 这种世界级流行的前端框架,换渲染函数(无论是 React.createElement 还是 JSX runtime 底层都一样的渲染逻辑,只不过名字换了而已)这种事情肯定有它自己的理由,为此它给出的推出的新转换的优势如下

优势

  • 使用全新的转换,你可以单独使用 JSX 而无需引入 React
  • 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小
  • 它将减少你需要学习 React 概念的数量,以备未来之需。

说实话这三点里面一、三点都非常离谱,首先是

第三点我实在是想不出来有什么减少的,难道 JSX 编译成 React.createElement 的八股不会变成 JSX 编译成 JSX runtime 的八股吗?

无需引入 React

还有第一点,在博客里面的语境意思就是,我们不再需要去写组件的时候,写上很别扭的一行引用

// React 17 RC 以前
import React from "react";

function App() {
  return <h1>Hello World</h1>;
}
// React 17 RC 及其以后
function App() {
  return <h1>Hello World</h1>;
}

为什么需要 import React from "react"; 是因为它的编译结果是需要 React.createElement,虽然在源代码里面没有用到,但是编译后是需要的,否则你会收到一个报错

Uncaught ReferenceError: React is not defined

但也恰恰是因为源代码(编译前)没有用到,像一些编译器或者包会频繁给你提醒,提示存在一个未使用的引用,如果小白不懂,按提示操作就会喜提上面的报错

减小打包体积

根据你的配置,JSX 的编译输出可能会 略微改善 bundle 的大小

这一点我认为是比较重要的(可以考察候选人对模块的理解)

为什么改变新转换以后还可以减小打包体积?举个例子

// React.createElement
// 手动引入 react
import React from 'react';

function App() {
  return React.createElement('h1', null, 'Hello world');
}
// JSX runtime
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

上面对于渲染函数的引用分为了两种方式

  1. _jsx 解构引用
  2. React.createElement 全量引用

直接引用 React 会将非 createElement 相关的 API (比如上面例子中就没有用到 useState)一并被打包进编译文件中,而使用解构,可以通过 tree-sharking 来排除掉项目中未使用到的 React API,这就是为什么 JSX runtime 可以减小打包体积

看到这里你应该明白了,其实 _jsx 可以理解为 createElement,如下

import { createElement } from 'react';

function App() {
  return createElement('h1', { children: 'Hello world' });
}

但因为在 React 17 RC 以前,编译结果只能是 React.createElement(...),因此无法修改成上面这种写法,但不知道出于什么原因 React 在改进的时候直接选择了换掉 React.createElement

Tree Sharking

解构就能减小打包体积的原因是什么呢?怎么减的?这里可以给出 rolluptree-sharking 编译结果的例子](https://rollupjs.org/repl/?version=3.7.5&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMiUyRiolMjBUUkVFLVNIQUtJTkclMjAqJTJGJTVDbmltcG9ydCUyME1hdGglMjBmcm9tJTIwJy4lMkZtYXRocy5qcyclM0IlNUNuJTVDbmNvbnNvbGUubG9nKCUyME1hdGguY3ViZSglMjA1JTIwKSUyMCklM0IlMjAlMkYlMkYlMjAxMjUlMjIlMkMlMjJpc0VudHJ5JTIyJTNBdHJ1ZSU3RCUyQyU3QiUyMm5hbWUlMjIlM0ElMjJtYXRocy5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjIlMkYlMkYlMjBtYXRocy5qcyU1Q24lNUNuZXhwb3J0JTIwZGVmYXVsdCUyMCU3QiU1Q24lMjAlMjBzcXVhcmUoeCklMjAlN0IlNUNuJTVDdCUyMCUyMHJldHVybiUyMHglMjAqJTIweCUzQiU1Q24lMjAlMjAlN0QlMkMlNUNuJTIwJTIwY3ViZSUyMCh4KSUyMCU3QiU1Q24lNUN0JTIwJTIwcmV0dXJuJTIweCUyMColMjB4JTIwKiUyMHglM0IlNUNuJTIwJTIwJTdEJTVDbiU3RCUyMiUyQyUyMmlzRW50cnklMjIlM0FmYWxzZSU3RCU1RCUyQyUyMm9wdGlvbnMlMjIlM0ElN0IlMjJmb3JtYXQlMjIlM0ElMjJlcyUyMiUyQyUyMm5hbWUlMjIlM0ElMjJteUJ1bmRsZSUyMiUyQyUyMmFtZCUyMiUzQSU3QiUyMmlkJTIyJTNBJTIyJTIyJTdEJTJDJTIyZ2xvYmFscyUyMiUzQSU3QiU3RCU3RCUyQyUyMmV4YW1wbGUlMjIlM0FudWxsJTdE))

输入:(React.createElement 形式)

// main.js
import Math from './maths.js';

console.log( Math.cube( 5 ) ); // 125
// maths.js

const square = function (x) {
    return x * x;
};

const cube = function (x) {
    return x * x * x;
}

export { square, cube };

export default {
  square,
  cube
}

输出:

// maths.js

const square = function (x) {
    return x * x;
};

const cube = function (x) {
    return x * x * x;
};

var Math = {
  square,
  cube
};

/* TREE-SHAKING */

console.log( Math.cube( 5 ) ); // 125

输入:(JSX runtime 形式)

// main.js
import { cube } from './maths.js';

console.log( Math.cube( 5 ) ); // 125
// maths.js

const square = function (x) {
    return x * x;
};

const cube = function (x) {
    return x * x * x;
}

export { square, cube };

export default {
  square,
  cube
}

输出:

// maths.js

const cube = function (x) {
    return x * x * x;
};

/* TREE-SHAKING */

console.log( cube( 5 ) ); // 125

在线查看完整代码](https://rollupjs.org/repl/?version=3.7.5&shareable=JTdCJTIybW9kdWxlcyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJtYWluLmpzJTIyJTJDJTIyY29kZSUyMiUzQSUyMiUyRiolMjBUUkVFLVNIQUtJTkclMjAqJTJGJTVDbmltcG9ydCUyME1hdGglMjBmcm9tJTIwJy4lMkZtYXRocy5qcyclM0IlNUNuJTVDbmNvbnNvbGUubG9nKCUyME1hdGguY3ViZSglMjA1JTIwKSUyMCklM0IlMjAlMkYlMkYlMjAxMjUlMjIlMkMlMjJpc0VudHJ5JTIyJTNBdHJ1ZSU3RCUyQyU3QiUyMm5hbWUlMjIlM0ElMjJtYXRocy5qcyUyMiUyQyUyMmNvZGUlMjIlM0ElMjIlMkYlMkYlMjBtYXRocy5qcyU1Q24lNUNuY29uc3QlMjBzcXVhcmUlMjAlM0QlMjBmdW5jdGlvbiUyMCh4KSUyMCU3QiU1Q24lNUN0cmV0dXJuJTIweCUyMColMjB4JTNCJTVDbiU3RCUzQiU1Q24lNUNuY29uc3QlMjBjdWJlJTIwJTNEJTIwZnVuY3Rpb24lMjAoeCklMjAlN0IlNUNuJTVDdHJldHVybiUyMHglMjAqJTIweCUyMColMjB4JTNCJTVDbiU3RCU1Q24lNUNuZXhwb3J0JTIwJTdCJTIwc3F1YXJlJTJDJTIwY3ViZSUyMCU3RCUzQiU1Q24lNUNuZXhwb3J0JTIwZGVmYXVsdCUyMCU3QiU1Q24lMjAlMjBzcXVhcmUlMkMlNUNuJTIwJTIwY3ViZSU1Q24lN0QlMjIlMkMlMjJpc0VudHJ5JTIyJTNBZmFsc2UlN0QlNUQlMkMlMjJvcHRpb25zJTIyJTNBJTdCJTIyZm9ybWF0JTIyJTNBJTIyZXMlMjIlMkMlMjJuYW1lJTIyJTNBJTIybXlCdW5kbGUlMjIlMkMlMjJhbWQlMjIlM0ElN0IlMjJpZCUyMiUzQSUyMiUyMiU3RCUyQyUyMmdsb2JhbHMlMjIlM0ElN0IlN0QlN0QlMkMlMjJleGFtcGxlJTIyJTNBbnVsbCU3RA==))

略微改善是为什么?

React 在优势上的措辞非常谨慎,它说

可能会 略微改善 bundle 的大小

这是因为,无论是 ESM 还是 CJS 对同一模块的导入,都是存在缓存的,也就是说你在 A 组件全量引用了 React,在 B 组件再引用一次是不会在打包文件中再加上一个 React并不是 1 + 1 的关系

// A
import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}
// B
import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}
// React 只会在磁盘被加载一次,往后都是缓存

性能优化和简化

其实 JSX runtime 对于 React.createElement 还有一定的性能优化和简化,但文章中没讲,所以我也不讲(绝对不是因为我菜而看不懂),相关链接 - 性能优化和简化

IMG

参考资料

  1. 介绍全新的 JSX 转换 - React - Luna Ruan
相关文章
|
1月前
|
前端开发 JavaScript 开发者
React:JSX语法入门
React:JSX语法入门
37 0
|
1月前
|
XML 前端开发 JavaScript
react中JSX的详解
react中JSX的详解
24 2
|
1月前
|
前端开发 JavaScript 安全
React中的JSX:语法与原理深入解析
【4月更文挑战第25天】本文深入解析React中的JSX,一种JavaScript语法扩展,使代码更直观。JSX让开发者以HTML样式描述组件UI,但最终编译成JavaScript。通过Babel转换,JSX标签转为React.createElement()调用,创建虚拟DOM。JSX的优势在于直观性、类型安全、代码复用和工具支持,助力高效开发React组件,适应不断发展的Web应用需求。
|
1月前
|
JavaScript 前端开发
vue3中使用jsx报错React is not defined和h is not defined
vue3中使用jsx报错React is not defined和h is not defined
|
1月前
|
前端开发 JavaScript 安全
react为什么要使用JSX
react为什么要使用JSX
39 1
|
1月前
|
XML 前端开发 JavaScript
【前端】深入了解React JSX语法及实例应用
【前端】深入了解React JSX语法及实例应用
25 0
|
1月前
|
JSON 前端开发 JavaScript
React源码解析-JSX
React源码解析-JSX
70 1
|
1月前
|
XML JavaScript 前端开发
说说React jsx转换成真实DOM的过程?
说说React jsx转换成真实DOM的过程
25 0
|
1月前
|
前端开发 JavaScript 开发者
React中JSX语法入门
React中JSX语法入门
|
1月前
|
前端开发 JavaScript
react JSX是什么,作用是什么
react JSX是什么,作用是什么
49 0

热门文章

最新文章