新的JSX转换方式——React 官方博客翻译

2,636 阅读7分钟

September 22, 2020 by Luna Ruan

虽然React 17没有什么新特性的出现,但是它提供了新版的JSX转换方式。在这篇文章里,我们会阐述它是什么以及如何体验它。

什么是JSX转换

浏览器没有办法直接理解JSX的语法,所以大多数的React用户都依靠像Babel和TypeScript这样的工具将JSX代码转化成为普通的JavaScript。许多像Create React App和Next.js这样的预置工具箱中也有包含这些转换工具。

随着React 17的发版,我们也希望在JSX的转换上能有一些提升,但是我们也不希望去破坏已有的设定。这也是为什么我们利用Babel一起为那些愿意更新的开发者提供了一个全新的重写的JSX转换方式

是否更新到新的转换方式是完全可选的,但是它也有一些好处。

  • 新的JSX转换方式可以不引入React
  • 依赖你的设置,它的编译输出的bundle文件可能会稍微大一些
  • 它能够支持减少你将来在React中学习的内容 这次更新不会改变JSX的句法并且也不是强制要求的。旧的JSX转换会像往常一样继续起作用,而且我们目前也没有想要移除它的支持。

React 17 RC已经支持这种新的转换方式,所以让我们来试一下吧!为了让它更容易被接受,我们也在React 16.14.0, React 15.7.0和React 0.14.10中支持了它。你可以查阅下方为不同的工具提供的升级方法。

现在让我们来看一下旧的和新的转换方式的不同。

新转换方式的不同

当你用JSX的时候,编译器将它转化成了浏览器能够读懂的React函数。在旧的转换中,它会将JSX转换为React.createElement(...)函数。

比如,你的代码如下所示

import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}

在基础设施中,旧的JSX转换方式会将它转换成普通的JavaScript

import React from 'react';

function App() {
  return React.createElement('h1', null, 'Hello world');
}

注意

你的代码不需要以任何方式改变。我们正在阐述JSX转换器是怎样将JSX源码转换成为浏览器能够理解的代码。

然而,这也不够完美:

  • 因为JSX被转化成了React.createElement,如果你使用JSX,React需要被放在一个命名空间里。
  • 有一些React.createElement不允许的性能提升和简化

为了解决这些问题,React 17 介绍了两个专门为像Babel和TypeScript这样的编译器规划的新的入口点。不同于将JSX转化成为React.createElement,新的JSX转换器会自动将特殊的函数从React包内的新入口点中引入并调用。

如果你的代码如下:

function App() {
  return <h1>Hello World</h1>;
}

这是新的转换器编译成的结果:

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';

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

注意我们的源码不需要再通过引入React再用JSX!(但是我们仍然需要通过引入React来使用Hooks和其他React提供的特性。)

这个改变是完全和当前的JSX代码兼容的,所以你不需要改变你的组件。如果你好奇是怎么实现的,你可以通过查看技术细节去看新的转换器是怎么工作的。

注意

在react/jsx-runtime和react/jsx-dev-runtime中的函数只能被编译转化器使用。如果你需要在你的代码中手工创建元素,你必须要继续使用React.createElement。它必须要继续起作用并且不能被抛弃。

如何更新到新的JSX转换器

如果你没有准备好更新到新的JSX转换器或者你正在另外一个库中使用JSX,旧的JSX转换器不会被移除并且会被继续支持。

如果你要更新,你需要两样东西:

  • 一个能够支持新转换器的React版本React 17 RC及更高的版本都支持它,但是我们已经在 React 16.14.0, React 15.7.0 和 React 0.14.10中为那些依然使用旧的主要老版本的人们提供新转换器的支持。

  • 一个可兼容的编译器(可以参考下方不同工具的说明)。

既然新的JSX转换器不需要React在特定的命名空间,我们已经准备了一个自动化的脚本去移除掉你的代码库中没有意义的引入。

Creat React App

Create React App 4.0.0+使用了一个新的转换器去兼容React 版本。

Next.js

Next.js v9.5.3+ 使用了新的转换器去兼容React 版本。

Gatsby

Gatsby v2.24.5使用了新的转换器去兼容React版本。

注意

如果你在更新到React 17 RC的时候遇到了Gatsby的错误,运行 npm update 去修复它。

手动Babel设置

新转换器的支持 Babel v7.9.0 及以上版本。

首先,你需要更新到最新的Babel版本和插件转换器。

如果你正在使用 @babel/plugin-transform-react-jsx:

# for npm users
npm update @babel/core @babel/plugin-transform-react-jsx
# for yarn users
yarn upgrade @babel/core @babel/plugin-transform-react-jsx

如果你在使用 @babel/preset-react:

# for npm users
npm update @babel/core @babel/preset-react
# for yarn users
yarn upgrade @babel/core @babel/preset-react

目前,在旧的转换器中,{"runtime": "classic"} 是一个初始配置。为了支持新的转化器,你可以使用 {"runtime": "automatic"} 这个选项在 @babel/plugin-transform-react-jsx 或 @babel/preset-react 中:

// If you are using @babel/preset-react
{
  "presets": [
    ["@babel/preset-react", {
      "runtime": "automatic"
    }]
  ]
}
// If you're using @babel/plugin-transform-react-jsx
{
  "plugins": [    ["@babel/plugin-transform-react-jsx", {      "runtime": "automatic"    }]
  ]
}

从Babel 8开始,"automatic"这个配置在两个插件中都是初始化配置。如果你想要了解更多,请查阅 Babel 文档 @babel/plugin-transform-react-jsx@babel/preset-react.

注意

如果你和JSX搭配使用的库不是React,你可以使用importSource这个配置项来从对应的库中引入——只要这个库提供了入口点。或者你也可以继续用我们要继续支持的那个传统的转换器。

如果你是一个库的作者并且你正在把/jsx-runtime这个入口点实施在你自己的库中,记住这里有一个新转换器为了兼容性不得不回退到createElement的例子。在这个例子中,它将会从被importSource简化过的根入口点中直接自动引入createElement。

ESLint

如果你正在使用eslint-plugin-react,react/jsx-uses-react 和 react/react-in-jsx-scope 中的规则将不必要,可以被移除。

{
  // ...
  "rules": {
    // ...
    "react/jsx-uses-react": "off",
    "react/react-in-jsx-scope": "off"
  }
}

TypeScript

TypeScript支持v4.1及以上的新JSX转换器。

Flow

Flow支持v0126.0及以上版本的新转换器, 通过增加 react.runtime=automatic 配置在你的Flow配置项中。

移除掉没用的React引用

因为新的转换器会自动引入必要的 react/jsx-runtime 函数,在使用JSX的时候没有必要在命名空间中引入了。这将会导致你的项目中有冗余的React引入,留着这些代码没有什么坏处,但是如果你想要移除它们,我们建议去运行codemod去自动移除它们。

cd your_project
npx react-codemod update-react-imports

注意

如果你在运行codemod的过程中出了错误,当运行 npx react-codemod update-react-imports 时询问你去选择一项时,尝试去简化JavaScript语法。尤其是当下JavaScript搭配Flow的设置支持的句法比单独配置JavaScript更新,即使你不用Flow。如果你遇到问题可以查看这个文件

记住codemod的输出不一定符合你的代码规范,所以在codemod运行完之后你应该运行一下Prettier去规范代码

运行codemod将会:

  • 在更新JSX转换器后移除所有的无用的React引用

  • 改变所有React默认引用(例如:import React from "react")为将来所推崇的重构后的命名式引用(如:import { useState } from "react")。这种改变不会影响已有的命名空间式的引用,(如:import * as React from "react")。原始引用将会在React 17中继续保留,但是长远打算我们还是建议移除它们。

例如,

import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}

将会被下面替代

function App() {
  return <h1>Hello World</h1>;
}

如果你在React中引用了其他东西,比如 Hook, 它就会被转化成一个实名引用。

例如,

import React from 'react';

function App() {
  const [text, setText] = React.useState('Hello World');
  return <h1>{text}</h1>;
}

将会被替代为

import { useState } from 'react';

function App() {
  const [text, setText] = useState('Hello World');
  return <h1>{text}</h1>;
}

此外为了清除无用的引用,这也将会帮助你为未来的主要的React版本(不是React 17)将会支持 ES Modules 并且不再支持原始导出。