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関数への変換機能が入っています。
まとめ
- Babelやtscのようなコンパイラー(トランスパイラー)がJSXをcreateElement関数呼び出しを行うJavaScriptコードに変換されます
- 変換されたコード実行時にcreateElement関数が呼び出されメモリー上にDOMが構築されます。これはvirtual DOM(仮想 DOM)と呼ばれています
- Reactライブラリーは、このvirtual DOMをブラウザーのAPI(
document.createElement
)を使い画面を作成します
以上のような仕掛けでJSXがHTML(ブラウザーの画面)に表示されます。
ただし、ブラウザーの全画面表示を毎回行うと表示時間がかかりすぎ快適なUXを実現できません。そこで現在の画面に対応するメモリー上のvirtual DOMと、2.で作られたvirtual DOMを比較し、差分のみをブラウザーのAPIで描くことで高速化しています。