JSXが実はベターな解だったのではないか?
JSXとHTMLベースのテンプレート言語の比較を行い、批判されがちなJSXが実はベターな解だったのでは?という記事です。
僕の結論は、HTMLとJSのどちらが制御構造を持てばいいのか?でいえばJS側が持つ方がリファクタリングしやすいため、JSXの方が良いというものです。
さて、先日、JSフレームワーク事情2020年始めという記事を書きました。これは、JavaScriptフロントエンドフレームワーク、Angularの人気が下落中という記事の元ソースであるThe State of JavaScript 2019を見ながら、React/Vue/Angularや、Next/Nuxt/Gatsbyが置かれている状況を解説するものでした。
他には、確証はないものの、Reactのシェアと人気がともに高い理由は、意外にJSXにもあるのではないか?と考えています。VueもAngularも基本的にはHTMLをベースとしたテンプレート言語を採用しています。Ruby on Railsの時代からその点は変わっていません。
JSXとはなにか
JSXは、JavaScript/TypeScriptのソースコードの中にHTMLに酷似したナニカを埋め込むものです。
他のテンプレートエンジンやテンプレートベースの言語とは考え方が逆で、JS/TSのコードが主体であり、そこにHTMLっぽい木構造を埋め込むための仕組みに過ぎません。
babelなどのトランスパイラはHTMLのようなものを、デフォルトではReact.createElement()という関数呼び出し、つまりJSのコードに置換します。文字列はいい感じにエスケープされますし、波括弧で囲われた部分もJavaScriptのコードとして使われます。
JSXは決して、テンプレートの中にロジックを埋め込むものではありません。
応用例としては、MDXというMarkdownの中にJSXを入れるものや、JSXを使ってPowerpointデータを作成するというものがあります。
このプレゼンを見ると分かりますが、JSXはただの木構造に過ぎません。こういう応用には無限の可能性を感じます。
HTMLテンプレートとは何か
HTMLテンプレートでは v-if v-for や ng-if ng-repeat という独自のディレクティブに制御用のコードとしてJSの断片やDSLを埋め込みます。HTMLが主であり、制御が必要な時にJSもしくはDSLを差し込むという考え方です。
これはRubyやPHPなどの従来のウェブアプリケーション開発の概念と同じであるため、考え方としては馴染みやすいものでしょう。
Rubyは、一部のほんの特殊な例外を覗いては、ウェブブラウザ上では動作しないため、Ruby/PHP、テンプレート言語、 JavaScript という組み合わせが使われてきました。
そもそも、古き良きHTML+JSの素朴な時代からHTMLの属性の中にJSのコードは紛れ込んでいました。
HTMLテンプレートは mustache などが有名なところでしょう。ウェブサイトの静的ジェネレータなど、様々なところでも利用されています。
AngularJS/AngularやVueもそういったHTMLテンプレートを踏襲しています。基本的にはHTMLとしてvalidなものを動的に書き換えるような考え方です。実際にはもっと最適化されてはいますが。
v-if や ng-if のようなディレクティブでは、そのディレクティブを持つタグとその中にあるものを、レンダリングするかどうかを制御します。
v-for や ng-repeat のようなディレクティブでは、イテラブルを展開し、複数の要素をレンダリングします。
個人的に感じるHTMLテンプレートの問題点
個人的にはこのやり方、とくにif分岐が存在することがテンプレートの肥大化につながり、シンプルさが損なわれると考えています。
<Hoge if="mode===1">...</Hoge>
<Fuga if="mode===2">...</Fuga>
... 以下 mode が幾つも続く
のような例は極端かもしれませんが、容易にこのようなコードが書かれてしまいます。
<Hoge if="flag">...</Hoge>
<Fuga else>...</Fuga>
を複雑と感じるかどうかはその人次第かもしれませんが、個人的には複雑性を感じます。see. 循環的複雑度
JSXの場合はそもそも分岐があるときには、
switch (mode) {
case 1:
return <Hoge>...</Hoge>
case 2:
return <Fuga>...</Fuga>
...
}
のようにJSXの主体であるJS/TS側で制御を行います。あるいは、予め変数に代入しておくやり方があります。
let content
switch (mode) {
case 1:
content = <Hoge>...</Hoge>
break
case 2:
content = <Fuga>...</Fuga>
break
...
}
return <Container>{content}</Content>
JSXではJS/TSが主体であるため、HTMLに制御構造が必要になっても、JS/TSのコードで木構造を制御すればいいだけです。リファクタリングも色々とやりようがあります。なんと言ってもJS/TSは本職のプログラミング言語であり、容易にユニットテストでもなんでも書けます。
・ JSXはJS/TSで木構造を制御・表現するために使われる
・ HTMLテンプレートでは静的に書かれた木構造の中に制御が埋め込まれる
VueやAngularJS/Angularを採用しているコードを読んだとき、HTMLに制御コードが埋め込まれることによる肥大化を度々見かけます。
また、ディレクティブを自作すると、ディレクティブによってどういう働きをするかという覚える・調べるべき情報量が増えます。これもディレクティブとして指定したDSLを制御するという、木構造をJS/TSで制御する以上の複雑性を持ったものです。
結論
JSXは、JS/TSのコードが主であり、単に木構造をコードの中で表現する拡張にすぎないため、変数に代入する、関数分割する、引数でやりとりするなど、いくらでもリファクタリングする余地があります。
HTMLテンプレートを使いディレクティブで制御するタイプのものは、どうしてもテンプレートの肥大化との戦いになるでしょう。(より良い方法があって肥大化せずに済むならぜひ教えて下さい)
制御構造という複雑性をHTML側に押し付けるのか?それともJS/TS側に押し付けるのか?という違いに過ぎませんが、JS/TSはプログラミング言語であるため、リファクタリングしやすいという点で有利だと思います。というだけの記事でした。