We want to hear from you!Take our 2020 Community Survey!

新しい JSX トランスフォーム

September 22, 2020 by Luna Ruan
日本語版サイト (ja.reactjs.org) のブログセクションへの記事掲載には英語版サイトと比べてタイムラグがあります。 最新のブログ記事は英語版でご確認ください。
日本語版サイトでは英語版ブログに定期的に追従しつつ、2020 年以降の記事を随時翻訳しています。

React 17 には新機能はありませんが、新バージョンの JSX トランスフォーム (JSX transform) に対応します。この記事ではこれがどのようなもので、どのように試せるのかについて説明します。

JSX トランスフォームとは?

ブラウザはそのままでは JSX を理解しないため、ほとんどの React ユーザは、Babel や TypeScript のようなコンパイラを利用して JSX コードを通常の JavaScript に変換 (transform) しています。Create React App や Next.js のような多くの設定済みツールキットも、裏では JSX トランスフォーム機能を搭載しています。

React 17 のリリースとともに、JSX トランスフォームにいくつかの改良を加えたいと思っていましたが、既存のセットアップを壊したくもありませんでした。そのため、Babel と協力して、アップグレードしたい人のために JSX トランスフォームの新バージョンを提供することにしました。

新しいトランスフォームへのアップグレードは完全に任意ですが、いくつかの利点があります。

  • 新版のトランスフォームでは、React をインポートせず JSX を使用できます。
  • セットアップによっては、コンパイル後のバンドルサイズをわずかに改善できる可能性があります。
  • 将来的に、React を学ぶために覚える必要がある概念の数を減らすような改善が可能になります。

このアップグレードは JSX の構文を変更するものではなく、必須でもありません。以前の JSX トランスフォームもこれまで通り動作し続けますし、それらのサポートを削除する予定もありません。

React 17 RC にはすでに新しいトランスフォームのサポートが含まれていますので、試してみてください。より簡単に導入できるよう、React 16.14.0、React 15.7.0、React 0.14.10 へのバックポートも行っています記事の後半で様々なツールにおけるアップグレード方法を記載しています。

では、新旧のトランスフォームの違いを詳しく見ていきましょう。

新しいトランスフォームは何が違うのか?

JSX を使うと、コンパイラはそれをブラウザが理解できる React の関数呼び出しに変換します。以前の JSX トランスフォーム機能では、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 では React パッケージに、Babel や TypeScript のようなコンパイラのみが使用することを意図した 2 つの新しいエントリポイントを導入しています。JSX を React.createElement に変換する代わりに、新しい JSX トランスフォームは、React パッケージのこれらの新しいエントリポイントから特別な関数を自動的にインポートし、それらを呼び出します。

ソースコードが以下のようになっているとしましょう。

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

新しい JSX トランスフォームは、これをこのようなコードにコンパイルします。

// 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' });
}

JSX を使用するために React をインポートする必要がなくなっていることに注意してください!(ただし、React が提供するフックやその他のエクスポートを使用するためには、引き続き React をインポートする必要があります)。

この変更は、既存のあらゆる JSX コードと完全に互換性がありますので、あなたのコンポーネントを変更する必要はありません。興味がある方は、新しいトランスフォームがどのように動作するかの詳細についてテクニカル RFC をチェックしてみてください。

補足

react/jsx-run-timereact/jsx-dev-run-time 内の関数は、コンパイラによるトランスフォーム機能によってのみ使用されなければなりません。コードの中で手動で要素を作成する必要がある場合は、React.createElement を使い続けるべきです。これは動作し続けますし、なくなることはありません。

新しい JSX トランスフォームへのアップグレード

新しい JSX トランスフォームにアップグレードする準備ができていない場合や、別のライブラリで JSX を使用している場合でも、心配は要りません。古いトランスフォームは削除されず、引き続きサポートされます。

アップグレードしたい場合は、以下の 2 つのものが必要です。

  • 新しいトランスフォームをサポートする React のバージョンReact 17 RC 以降でサポートされますが、以前のメジャーバージョンに留まりたい方のために React 16.10.0、React 15.7.0、React 0.14.10 もリリースしてあります)。
  • 互換性のあるコンパイラ(下記のツールごとの説明を参照)。

新しい JSX 変換は React をスコープに入れる必要がないので、コードベースから不要なインポートを削除する自動化スクリプトも用意しました

Create React App

Create React App でのサポートはすでに追加されており、現在ベータテスト中の次期 v4.0 リリースで利用可能になる予定です。

Next.js

Next.js v9.5.3 以降、互換性のある React のバージョンに対して新しいトランスフォームを利用します。

Gatsby

Gatsby v2.24.5 以降、互換性のある React のバージョンに対して新しいトランスフォームを使用します。

補足

React 17 RC にアップグレードした後にこの Gatsby エラーが出た場合は、npm update を実行することで修正できます。

Babel の手動セットアップ

新しい JSX トランスフォームのサポートは、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") がデフォルトのオプションです。新しいトランスフォームを有効にするには、@babel/plugin-transform-react-jsx ないし @babel/preset-react のオプションとして {"runtime": "automatic"} を渡してください。

// 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-reactreact/react-in-jsx-scope のルールは不要になりますので、無効にするか削除することができます。

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

TypeScript

TypeScript は新しい JSX トランスフォームを v4.1 beta でサポートします。

Flow

Flow は新しい JSX トランスフォームを v0.126.0 およびそれ以降でサポートします。

未使用 React インポートの削除

新しい JSX トランスフォームは、必要とする react/jsx-runtime 関数を自動的にインポートするため、JSX を使用する際に React をスコープに入れる必要がなくなります。これにより、コードの中で React のインポートが未使用となる可能性があります。残しておいても害はありませんが、削除したい場合は “codemod” スクリプトを実行して自動的に行うことをお勧めします。

cd your_project
npx react-codemod update-react-imports

補足

codemod の実行時にエラーが出る場合は、npx react-codemod update-react-imports で別の JavaScript の方言を指定してみてください。特に、現時点では “JavaScript with Flow” の設定は、“JavaScript” の設定よりも新しい構文をサポートしていますので、Flow を使用していない場合でも試してみてください。問題が発生した場合は issue を報告 してください。

また、codemod の出力はあなたのプロジェクトのコーディングスタイルと必ずしも一致はしませんので、一貫したフォーマットにするため codemod の終了後に Prettier を実行すると良いかもしれません。

この codemod を実行すると以下のことを行います:

  • 新しい JSX トランスフォームにアップグレードした結果使用されなくなる React のインポートをすべて削除します。
  • すべての React のデフォルトインポート(すなわち、import React from "react")を、将来的に望ましいスタイルである分割代入型の名前付きインポート(例えば、import { useState } from "react")に変更します。名前空間インポート(つまり、import * as React from "react")も有効な形式ですが、codemod はこれらは変換しません。デフォルトインポートは React 17 でも動作し続けますが、長期的にはそれらのインポートを利用しないようにすることを推奨します。

例えば、

import React from 'react';

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

は、以下に置き換わります:

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

他のもの(例えばフック)を React からインポートしている場合は、codemod はそれらを名前付きインポートへと変換します。

例えば、

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>;
}

未使用のインポートをクリーンアップすることに加えて、これは ES Modules をサポートしデフォルトエクスポートを持たない将来のメジャーバージョンの React(React 17 のことではありません)への準備にも役立ちます。

謝辞

新しい JSX トランスフォームの実装と統合を手伝っていただいた Babel、TypeScript、Create React App、Next.js、Gatsby、ESLint および Flow のメンテナの方々に感謝します。また、テクニカル RFC においてフィードバックや議論をしていただいた React コミュニティにも感謝します。

Is this page useful?このページを編集する