EY-Office ブログ

JSXにいたる歴史と考察(おまけ)

JSXにいたる歴史と考察(2/2)の続きです。JSXにいたる歴史と考察(2/2)の最後に書いた今回書けなかった事を書こうと思います。

  • JSX(React)の歴史
  • JSXはどのように実装されているのか

歴史

JSX(React)の歴史

JSXの歴史を調べてみたかったのですが、ネットを検索したレベルでは見つかりませんでした。 しかし、The History of React.js on a Timelineというページを見つけました。このページの最初の方に

2010 – The first signs of React

XHPというPHP拡張の事が書かれています。これは

<?php
$href = 'http://www.facebook.com';
echo <a href={$href}>Facebook</a>;

<?php
$list = <ul />;
foreach ($items as $item) {
  $list->appendChild(<li>{$item}</li>);
}

のように、PHPにHTML(XML)リテラルを導入したものです!
これがJSXのご先祖様だと思われます😊

また、独自HTMLタグ(コンポーネント)をPHPのクラスで定義できるのはReactの先祖らしいですね。

JSXはどのように実装されているのか

JSXはどのな仕掛けで通常のHTML(DOM)に変換されるのでしょうか?
これには2つの技術が関わっています、

  • HTML(DOM)を生成するReact.createElement()関数
  • JSXからReact.createElement()呼び出しコードに変換するコンパイラー(トランスパイラーとも呼ばれます)

React.createElement

React公式サイトのJSX なしで React を使うに簡単なReact.createElementの説明があります。

createElementのTypeScript型定義を、独断で簡略化したものは以下のようになります。

function createElement(
  type: FunctionComponent| ComponentClass | string,
  props?: Attributes | null,
  ...children: ReactNode[]): ReactElement;
  • 第1引数は、ReactのコンポーネントまたはHTMLタグの文字列
  • 第2引数は、Reactのコンポーネントの引数、またはHTMLタグの属性
  • 第3引数以降は、子要素で複数受け取れます

です。

シンプルな例

const Hello1 = () => {
  return <div>Hello world!</div>;
}

↓ タグ名はdiv、子要素はHello world!文字列です、属性はないのでnullです。

const Hello1 = () => {
  return React.createElement("div", null, "Hello world!");
}

複数タグの例

const Hello2 = () => {
  return (
    <div>
      <h1>Hello</h1>
      <span>begin {1 + 2} end</span>
    </div>
  );
}

↓ 複数のタグがあり、さらにネストしている例です

  • divタグ用createElement関数の子要素には、第3,4引数にh1,span用のcreateElement関数が渡されています
  • spanタグ用createElement関数の子要素begin {1 + 2} endの部分は、文字列 “begin ”, JavaScriptの式 1 + 2, 文字列” end”の3引数に分解されています
const Hello2 = () => {
  return React.createElement("div", null,
    React.createElement("h1", null, "Hello"),
    React.createElement("span", null, "begin ", 1 + 2, " end")
  );
}

コンポーネント呼び出しの例

const Hello3 = ({word}) => {
  return (
    <h1>Hello {word}</h1>
  );
}

const HelloWord = () => {
  return (
    <Hello3 word="World!" />
  )
}

↓ 引数がある場合、仮引数はcreateElement関数の第2引数にオブジェクトで指定されています

const Hello3 = ({word}) => {
  return React.createElement("h1", null, "Hello ", word);
}

const HelloWord = () => {
  return React.createElement(Hello3, {word: "World!"});
}

繰り返しを含む例

const App = () => {
  const items = [
    {name: "チョコレート", price: 300},
    {name: "おはぎ", price: 200}
  ];
  return (
    <table>
      <thead>
        <tr><th>商品</th><th>価格</th></tr>
      </thead>
      <tbody>
      {items.map((item, ix) =>
        <tr key={ix}><td>{item.name}</td><td>{item.price}</td></tr>
      )}
      </tbody>
    </table>
  );
}

{items.map...は式なのでそのまま第3引数に渡されます

const App = () => {
  const items = [
    {name: "チョコレート", price: 300},
    {name: "おはぎ", price: 200}
  ];

  return React.createElement("table", null,
    React.createElement("thead", null,
      React.createElement("tr", null,
        React.createElement("th", null, "商品"),
        React.createElement("th", null, "価格")
      )
    ),
    React.createElement("tbody", null,
      items.map((item, ix) =>
        React.createElement("tr", {key: ix},
          React.createElement("td", null, item.name),
          React.createElement("td", null, item.price)
        )
      )
    )
  );
}

この変換を手動で行うのは手間ですが、変換ルールは単純ですね。BabelサイトのTry it outで試す事ができます。

コンパイラー(トランスパイラー)

コンパイラーというとWikipdiaのコンパイラにあるようにソースコードから機械語を生成する、C言語やRust, Swift言語のコンパイラーを想像する人も多いかと思います。そのような事情からトランスパイラーという言葉を使う事もあります。

JSXからcreateElement関数への変換を行うコンパイラー(トランスパイラー)としては Babelが有名です。Babelは元々最新のECMAScript(例、ES2021、ES6)を旧式なJavaScript(例、ES5)に変換するツールとして使われていましたが、プラグインとしてJSXを変換する機能が提供されておりReactの開発ツール(例、Create React App)の中でES6→ES5の変換と共にJSXからcreateElement関数への変換が行われています。

また、TypeScriptからJavaScriptに変換するツールtscにもJSXからcreateElement関数への変換機能が入っています。

まとめ

  1. Babelやtscのようなコンパイラー(トランスパイラー)がJSXをcreateElement関数呼び出しを行うJavaScriptコードに変換されます
  2. 変換されたコード実行時にcreateElement関数が呼び出されメモリー上にDOMが構築されます。これはvirtual DOM(仮想 DOM)と呼ばれています
  3. Reactライブラリーは、このvirtual DOMをブラウザーのAPI(document.createElement)を使い画面を作成します

以上のような仕掛けでJSXがHTML(ブラウザーの画面)に表示されます。

ただし、ブラウザーの全画面表示を毎回行うと表示時間がかかりすぎ快適なUXを実現できません。そこで現在の画面に対応するメモリー上のvirtual DOMと、2.で作られたvirtual DOMを比較し、差分のみをブラウザーのAPIで描くことで高速化しています。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ