Reactのアーキテクチャを知る JSXのトランスパイル

こんにちは、noyanです。React内部実装アドベントカレンダーの6日目です。前回でuseStateが一段落したので、1回で終わる簡単な記事を書きます。

JSXはフレームワーク非依存のJavaScript構文拡張で、宣言的にUIを記述することを可能にします。多くの人は親しみがあるものの、中身について知らない人も多いのではないでしょうか。現に、React17で入ったJSXの新たな変換について「改善」として紹介しつつも、改善点を挙げていない記事がいくつか見受けられます。

もちろんJSXのトランスパイルを知らなければJSXを使えないなんてことはありません。しかしJSXの知識は、内部実装を読むにあたって必須の知識であるとともに、他

JSXの変換先

前述の通りJSXは拡張構文で、これはbabelなどを通して変換されます。Reactの場合、JSXは次のうちどちらかに変換されます。

// もともと
const Hoge = () => <div>hoge</div>;

// "runtime": "classic"
/*#__PURE__*/React.createElement("div", null, "hoge");

// "runtime": "automatic"
/*#__PURE__*/_jsx("div", {
  children: "hoge"
});=

この_jsxReact.createElementはだいたい同じことをしていますが、すでに知られているように利便性とパフォーマンスの面で_jsxが有利です。

React.createElement_jsxの違いに、_jsxはトランスパイル時にインポート文が自動挿入されるため自分でimport文を書かなくて良いことが挙げられます。内部的にはパフォーマンスを向上させたり、spread構文でkeyが渡ることを非推奨にする準備として_jsxを導入しているようです。

とくにkeyの取り扱いは面白くて、_jsxでは常に{...props}内にkeyがあればそれが優先されますが、

createElementではマージする順番でkeyが変わります。いずれkey spreadingは非推奨になる可能性があるので、多少記憶にとどめておくと良いでしょう。

// props = {key:11}のとき
<div key={12} {...props}/>
// key = 11

<div {...props} key={12}>
// key = 12

以上の動作は次のレポジトリから確認できます。

https://github.com/noyanyan/babel-jsx-transform

JSX構文が何にトランスパイルされるかはトランスパイラの設定で変えることができます。

https://babeljs.io/docs/en/babel-preset-react#docsNav

フレームワークのJSX変換については、babelのpragma, tsconfigのjsxFactoryを変えることで観察できます。vueに関してはpotato4dさんの記事が勉強になります。

https://d.potato4d.me/entry/20200830-tsx-in-vue/

ReactElement

細かい処理の違いはこれで終わりにして、jsxcreateElementはどちらもReactElementを返します。

これは5つのプロパティ($$typeof, type, key, ref, props, _owner)を持つオブジェクトを返します。

export function jsx(type, config, maybeKey) {
  return ReactElement(
    type,
    key,
    ref,
    undefined,
    undefined,
    ReactCurrentOwner.current,
    props,
  );
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE, // symbolFor('react.element')

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };
  
  return element;
}

https://github.com/facebook/react/blob/12bffc78d8d9e0ee8d494849f20611fe15d598ef/packages/react/src/jsx/ReactJSXElement.js#L210

おわりに

本記事ではJSX構文が_jsxを呼び出し、ReactElementを作成しているところを確認しました。

React.Elementは5つプロパティをもつオブジェクトをつくることを大仰に説明している感はありますが...。