バニラJSXの事例
JSXがDOM要素を返すなら?
ClickMe 関数はボタンを生成し、クリック回数を表示する機能を持つ
- ボタンをクリックするたびにテキストが更新される
export default function ClickMe() {
let i = 0;
const el = Click me as HTMLButtonElement;
el.onclick = (e) => {
el.textContent = `Clicked ${++i} times`;
};
return el;
}
再利用性
ClickMe コンポーネントを複数回使用して、それぞれ異なる状態を維持できる
import ClickMe from "./sample1.js";
export default () =>
;
インタラクティブなDOMツリーの作成
TodoInput と TodoList クラスを使ってToDoリストを管理できる
- 項目を追加し、クリックして削除できる
function TodoInput(attrs: { add: (v: string) => void }) {
const input = as HTMLInputElement;
input.placeholder = 'Add todo item...';
input.onkeydown = (e) => {
if (e.key === 'Enter') {
attrs.add(input.value);
input.value = '';
}
};
return input;
}
class TodoList {
ul = as HTMLUListElement;
add(v: string) {
const item =
- {v}
as HTMLLIElement;
item.onclick = () => item.remove();
this.ul.append(item);
}
}
export default () => {
const list = new TodoList();
list.add('foo');
list.add('bar');
return
list.add(v)} />
{list.ul}
;
};
大量データの処理
FindNames 関数は大量のデータを処理・フィルタリングして結果を表示する
- 入力値に応じて一致する項目をリアルタイムで更新する
import { data } from "../fetch-dataset.js";
export default function FindNames() {
const status = as HTMLParagraphElement;
const results = as HTMLUListElement;
const input = as HTMLInputElement;
updateMatches();
function updateMatches() {
const matched = (data.entries().filter(([k]) => k.match(input.value)).toArray());
const matches = (Iterator.from(matched).map(match => ).take(30));
results.replaceChildren(...matches);
status.textContent = `${matched.length} / ${data.size}`;
}
return
{input}
{status}
{results}
;
}
function Item(attrs: { match: [string, number], regex: string }) {
const [name, count] = attrs.match;
const total = ({count});
return
-
{total}
;
}
function highlight(str: string, regex: string) {
if (!regex) return str;
const r = new RegExp(`(${regex})`, 'gi');
return str.replace(r, '$1');
}
imlibの紹介
imlib は immaculatalibrary.com のために開発されたライブラリである
- minigamemaker.com と、いま読んでいるウェブサイトの構築に使われている
- 既存の状態管理では十分でなかったために開発され、アプリを作るうえで最も好まれる方法である
GN⁺のまとめ
- この記事は、JSXを使ってDOM要素を直接生成し、相互作用させる方法を説明している
- 従来の仮想DOMを使わなくても、効率的に大量データを処理できる方法を示している
imlib ライブラリは、シンプルで直感的な方法でアプリを開発できるようにする
- 類似の機能を持つ他のプロジェクトには React、Vue.js などがある
1件のコメント
Hacker News の意見
実際の DOM ノードを返すと、JSX や Lit のような非 JSX ライブラリの大きな利点である即時モード風 API と
UI=f(state)モデルがほとんど無力化される本物の DOM ではなく DOM の記述を返すことで、新しい状態でテンプレートを繰り返し評価し、そのテンプレートがレンダリングする DOM を効率よく更新できる
例では
input.onkeydown = ...やthis.ul.append(item)のように命令型 DOM API で更新しているが、こうしたコードを避け、テンプレート内で項目を反復することこそが 仮想 DOM の主な利点である可能性が高いただし仮想 DOM の問題は差分計算が遅い点で、Lit のように静的部分と動的部分を分離するテンプレートで解決できる
むしろ「大きな UI を好きなだけ再レンダリングしても仮想 DOM が面倒を見てくれるだろう」と考えさせて逆効果になり、実際の性能は悪いのに、アプリ構造はすでに書き直しなしでは直しにくい形に固まってしまう
4つのチームで同じ流れを見たし、アーキテクトとして React で遭遇する問題を根拠に性能を意識したバニラ構成を勧めたが、「避ける方法はいくらでもある」という感じで受け入れられなかった
結局、多くの場合は自分で苦労して学ぶしかないようだ
createElement関数呼び出しパターンの別 DSL にすぎず、説明されている利点はフレームワークとランタイムから来ているもっと短く言えば、JSX は有用な適用先のある代替的な関数呼び出し構文である
以前の会社では JSX をテストデータファクトリに使っていた。XML のように見えるが、要素生成の段階でビルダーパターンにより、最終データに合わせた文脈上の判断ができた
React や DOM とは無関係で、JSX なしでは宣言的に表現できないという意味でもなかった
個人的には DOM を 命令型に更新する方が好きだ。何が起きるかを非常に細かく制御でき、とても効率的な構造として設計できる
誰かの強い方法論に合わせて考えなくても、アプリの機能を追加・変更・削除・修正しやすくできる
UI=f(state)モデルが弱まるという点には同意するが、これはコンピューティング全体でも指折りの 漏れのある抽象化のように感じるReact 周辺に、レンダリングを何度も行わない方法や不要な再描画を防ぐ方法といったエコシステムが生まれたのには理由がある
過去10年間、静的サイトジェネレーターの状況に不満を感じてこのプロジェクトを作った
主に静的 Web サイトを作っているので、シンプルで直感的なツールが欲しく、JSX はよく合いそうに見えたが、React のような JSX フレームワークの過剰な複雑さにはうんざりしていた
そこで JSX を文字列にレンダリングする静的サイトジェネレーターを作り、それをブラウザー側へ拡張して JSX を DOM 要素としてレンダリングするようにするのは自然だった
一部の場合、特にレイアウトでは共有コンポーネントにもよく合う。全体として成果には満足しているが、一部はかなりハック的で、IDE サポートももっと良くできる
さらにこの方式は SEO にもよく合う。他の JSX フレームワークでうまく解決されたと感じられなかった問題だった
ドキュメントでは「SSG」ではなく「static site generator」と書き下した方がよさそう。静的サイトジェネレーターを使ったことがあるのに SSG が何なのか分からず、AI に聞く必要があった
Vue から来たので驚いたが、lit-html は単純なバニラ JS テンプレートだけで、イベントの紐付けを含め Vue テンプレートが行う多くのことを実現してくれる
VSCode の lit 拡張を使うと、テンプレート内でも構文ハイライトと完全な型チェックを受けられる
Marc Grabanski が Lit ではなく vanilla JS と一緒に lit-html を使っているとツイートした後に知り、自分で試してみると非常にうまく動いた。今解決しようとしていることとかなり似ているように見える
lit-htmlテンプレートパッケージを使えば、テンプレートの章で説明されている大部分を実現できる: https://lit.dev/docs/templates/overview/lit-elementのコンポーネント抽象化なしでも可能: https://lit.dev/docs/libraries/standalone-templates/#renderi...このような「もしも」が面白いのは、JSX の起源を Facebook の XHP までさかのぼることができ、XHP はここで説明されているライブラリと似た見た目と挙動をしていた初期の JS 標準 E4X から明示的に着想を得ていたため
[1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...
[2] https://en.m.wikipedia.org/wiki/ECMAScript_for_XML
JSX が E4X を乗り越えた理由もそこにあり、その後 JS 内の HTML テンプレートについて真剣な提案は出てこなかった
それでも現代風に磨き直して、一般的なアイデアを復活させることはできそう: https://github.com/WICG/webcomponents/issues/1069
for eachではなくfor(of)が入った理由は E4X であるfor (:)を得られなかった理由はさらにばかげていて、一部の TC39 メンバーが、いつか言語に魔法のように入ると信じていた:type注釈と衝突する可能性があったためJSX テンプレート式が実際の DOM ノードを作るというアイデアで UI ライブラリを作ったことがある
モデルオブジェクトをプロパティにバインドして、命令的なイベントハンドラの定型コードも一部なくしてくれる。当然そう考えるだろうが、良いアイデアだと思う
https://github.com/tomtheisen/mutraction
track({ clicks: 0 })でモデルを作り、クリック時にmodel.clicksを増やし、その値を DOM に直接反映する、といったコードを書けるここでは VanJS にも触れておく価値がある: https://vanjs.org/
Solid.JS のような他の JSX ライブラリも DOM ノードを返しており、こうしたアイデアが勢いを得ているのは良いことだ
使っているプラットフォームに近づくほど良いと思う。抽象化レイヤーが有用な場合もあるだろうが、実際にはプラットフォームを隠す必要性はまだ見いだせていない
Facebook のような巨大プロジェクトでは恩恵があるかもしれないが、その規模で働いたことはない
例は素晴らしいが、時間とともに変わり得る コンポーネント props を示していない点が重要に見える
その能力自体がモデル化されていないようだ。不要なら賢いやり方だが、より複雑なアプリへ拡張されるイメージはあまり見えない
違いは、一般的な方法で DOM 要素を作成して設定する 10 行程度のコードを、より簡潔に書いているだけである
実際、このフレームワークで本当に興味深い部分はそれ以外のほぼすべてだ。静的サイトジェネレーター側で JSX を文字列として返すこと、生のソースディレクトリを取り込んで SSG 時点で
string|Bufferを操作すること、chokidarとswcの上に自作した非常に効率的で高速なモジュールシステムなどがあるJSX を DOM として使う部分は視覚的に最も目立つだけで、実際にはパーティートリックに近い
例として vanillajsx.com のソースは非常に簡潔で明確、かつ短い。
imlib以外には依存せず、今日 1 日で全部書いたもので、JSX-as-DOM のデモは最も革新的でない部分だ: https://github.com/sdegutis/vanillajsx.com/tree/main/siteJSX が具体的な DOM 要素を返すこの方式が好きなら、Solid はまさにこのように動作し、そこにきちんとしたリアクティビティ層が追加されている
setTimeoutやsetIntervalの使用をどう防ぐのか分からないただし、こうしたプロジェクトは小さなユースケースではうまく機能するが、「現実世界」のシナリオではすぐに崩れることが多いという点には同意する
実装はそれほど難しくなさそう
これは Vanilla TSX と非常によく似ている: https://github.com/wisercoder/uibuilder
Vanilla TSX で書いたアプリの例はこちらにある: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
言語の中核に XML があった ActionScript 3 を思い出す
書いていて楽しい言語だったが、ES4 になることには有名な失敗をした。結局 TypeScript と JSX で似た地点に到達するまでに 10 年以上かかったことになる
React の最初のリリースの 6 か月前である Firefox 17 で、Web ページ向けには無効化された
最後の例が Firefox で動かない人がほかにもいるのか気になる。Edge では動いたが、Firefox では失敗した
Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a functionJSX が以下のように書くよりなぜ魅力的なのか、よく理解できない
h("div", {}, [ h("p", {}, "this is easy"), ...list.map((l) => h("li", {}, l)) ])こうすれば、コンパイラや新しい構文を作らなくても、ループや変数の補間などを自然に得られる。誰か説明してくれるとうれしい
公平に言えば、JSX でも通常のループや補間をそのまま使う。
{}はバニラ JS の式を受け取るただし、要素タグを
createElement呼び出しに展開するコンパイル段階が必要なだけ人によって情報の処理の仕方は違うのだろうが、その例は句読点が多く、素早く把握するのが非常に難しい
頭の中で構文を自分で分解しないと生成される DOM 構造が見えないので、コンパイラが必要かどうかよりも、DOM 構造を一目で理解できることのほうがはるかに重要だ
ちなみに自分はニューロダイバーシティの当事者で、この説明が役に立てばと思う
ブラウザの開発者ツールで見るものに近いので、比較やデバッグがしやすい
しかし複雑なアプリでコンポーネントが数百個になり、互いにインポートして使い始めると、JSX タグで書く方法がシンプルで理解しやすい構文のように感じられる
React への反発も結局、単純なことには過剰だというところから多く出ていて、実際に過剰だ
ただし、人によって作るアプリや複雑さ、要件は異なるので、どちらか一方がより魅力的だと単純化することはできない。異なる問題のための異なるツールだ