3 ポイント 投稿者 GN⁺ 2024-08-17 | 1件のコメント | WhatsAppで共有

バニラ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ツリーの作成

  • TodoInputTodoList クラスを使って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件のコメント

 
GN⁺ 2024-08-17
Hacker News の意見
  • 実際の DOM ノードを返すと、JSX や Lit のような非 JSX ライブラリの大きな利点である即時モード風 API と UI=f(state) モデルがほとんど無力化される
    本物の DOM ではなく DOM の記述を返すことで、新しい状態でテンプレートを繰り返し評価し、そのテンプレートがレンダリングする DOM を効率よく更新できる
    例では input.onkeydown = ...this.ul.append(item) のように命令型 DOM API で更新しているが、こうしたコードを避け、テンプレート内で項目を反復することこそが 仮想 DOM の主な利点である可能性が高い
    ただし仮想 DOM の問題は差分計算が遅い点で、Lit のように静的部分と動的部分を分離するテンプレートで解決できる

    • 複雑な Web UI を作ってきた経験からすると、十分に複雑なアプリにおける 仮想 DOM の性能上の利点は、ほとんど幻想に近いと思う
      むしろ「大きな UI を好きなだけ再レンダリングしても仮想 DOM が面倒を見てくれるだろう」と考えさせて逆効果になり、実際の性能は悪いのに、アプリ構造はすでに書き直しなしでは直しにくい形に固まってしまう
      4つのチームで同じ流れを見たし、アーキテクトとして React で遭遇する問題を根拠に性能を意識したバニラ構成を勧めたが、「避ける方法はいくらでもある」という感じで受け入れられなかった
      結局、多くの場合は自分で苦労して学ぶしかないようだ
    • これはユーザーによる。別の見方をすれば、JSXcreateElement 関数呼び出しパターンの別 DSL にすぎず、説明されている利点はフレームワークとランタイムから来ている
      もっと短く言えば、JSX は有用な適用先のある代替的な関数呼び出し構文である
      以前の会社では JSX をテストデータファクトリに使っていた。XML のように見えるが、要素生成の段階でビルダーパターンにより、最終データに合わせた文脈上の判断ができた
      React や DOM とは無関係で、JSX なしでは宣言的に表現できないという意味でもなかった
    • 「望んでいるもの」は誰かによって違う。個人的には、あらゆる 調整(reconciliation)機構なしに DOM ノードをその場で更新する方が好みだ
    • 「もう命令型更新をしなくてよい」という夢は理解できる。こうしたフレームワークを10年ほど使ってきて、十分に慣れている
      個人的には DOM を 命令型に更新する方が好きだ。何が起きるかを非常に細かく制御でき、とても効率的な構造として設計できる
      誰かの強い方法論に合わせて考えなくても、アプリの機能を追加・変更・削除・修正しやすくできる
    • 実際の DOM ノードを返すと、JSX や Lit のような非 JSX ライブラリの利点である即時モード風 API と UI=f(state) モデルが弱まるという点には同意するが、これはコンピューティング全体でも指折りの 漏れのある抽象化のように感じる
      React 周辺に、レンダリングを何度も行わない方法や不要な再描画を防ぐ方法といったエコシステムが生まれたのには理由がある
  • 過去10年間、静的サイトジェネレーターの状況に不満を感じてこのプロジェクトを作った
    主に静的 Web サイトを作っているので、シンプルで直感的なツールが欲しく、JSX はよく合いそうに見えたが、React のような JSX フレームワークの過剰な複雑さにはうんざりしていた
    そこで JSX を文字列にレンダリングする静的サイトジェネレーターを作り、それをブラウザー側へ拡張して JSX を DOM 要素としてレンダリングするようにするのは自然だった
    一部の場合、特にレイアウトでは共有コンポーネントにもよく合う。全体として成果には満足しているが、一部はかなりハック的で、IDE サポートももっと良くできる
    さらにこの方式は SEO にもよく合う。他の JSX フレームワークでうまく解決されたと感じられなかった問題だった

    • Astro は見たことがあるのか気になる。完璧な静的サイトジェネレーターのように感じるが、VanillaJSX が解決する別の不満があったのかもしれない
    • アイデアは良い。デモページの ToDo アプリは 404 になる
      ドキュメントでは「SSG」ではなく「static site generator」と書き下した方がよさそう。静的サイトジェネレーターを使ったことがあるのに SSG が何なのか分からず、AI に聞く必要があった
    • Django のサーバーサイド向け JSX があったら素晴らしそう
    • lit-html は見てみたのか気になる
      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

    • E4X には実際の DOM インスタンスを返すという不幸な欠点があり、そのため命令的に更新する必要があった
      JSX が E4X を乗り越えた理由もそこにあり、その後 JS 内の HTML テンプレートについて真剣な提案は出てこなかった
      それでも現代風に磨き直して、一般的なアイデアを復活させることはできそう: https://github.com/WICG/webcomponents/issues/1069
    • 面白い事実として、JavaScript に 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 のような巨大プロジェクトでは恩恵があるかもしれないが、その規模で働いたことはない

    • バンドルサイズ比較では SolidJS はあまり意味がないのではないか?
  • 例は素晴らしいが、時間とともに変わり得る コンポーネント props を示していない点が重要に見える
    その能力自体がモデル化されていないようだ。不要なら賢いやり方だが、より複雑なアプリへ拡張されるイメージはあまり見えない

    • ここやブラウザ側コード全般で使っている手法は、VS Code 内部で使われている方法とまったく同じで、かなりうまくスケールする
      違いは、一般的な方法で DOM 要素を作成して設定する 10 行程度のコードを、より簡潔に書いているだけである
      実際、このフレームワークで本当に興味深い部分はそれ以外のほぼすべてだ。静的サイトジェネレーター側で JSX を文字列として返すこと、生のソースディレクトリを取り込んで SSG 時点で string|Buffer を操作すること、chokidarswc の上に自作した非常に効率的で高速なモジュールシステムなどがある
      JSX を DOM として使う部分は視覚的に最も目立つだけで、実際にはパーティートリックに近い
      例として vanillajsx.com のソースは非常に簡潔で明確、かつ短い。imlib 以外には依存せず、今日 1 日で全部書いたもので、JSX-as-DOM のデモは最も革新的でない部分だ: https://github.com/sdegutis/vanillajsx.com/tree/main/site
    • このアプローチを実際の規模まで大きくし、すべてが期待どおり一緒に動くと確信するには、きちんとした リアクティビティ層 が必要になる
      JSX が具体的な DOM 要素を返すこの方式が好きなら、Solid はまさにこのように動作し、そこにきちんとしたリアクティビティ層が追加されている
    • 何か見落としているのかもしれないが、この方式が setTimeoutsetInterval の使用をどう防ぐのか分からない
      ただし、こうしたプロジェクトは小さなユースケースではうまく機能するが、「現実世界」のシナリオではすぐに崩れることが多いという点には同意する
    • このライブラリを擁護するつもりも使う予定もないが、React/Vue のように props の変化が再レンダリングを引き起こすようにするなら、ここでは JS Proxy を使えばよさそうだ
      実装はそれほど難しくなさそう
  • これは 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 年以上かかったことになる

    • https://en.wikipedia.org/wiki/ECMAScript_for_XML — Firefox にもあったが、大衆は望まず、最終的に削除された
      React の最初のリリースの 6 か月前である Firefox 17 で、Web ページ向けには無効化された
    • このように XML をインライン生成できた記憶はない。おそらく Flex のサーバー側機能だったなら可能だったのかもしれない
  • 最後の例が Firefox で動かない人がほかにもいるのか気になる。Edge では動いたが、Firefox では失敗した
    Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function

  • JSX が以下のように書くよりなぜ魅力的なのか、よく理解できない
    h("div", {}, [ h("p", {}, "this is easy"), ...list.map((l) => h("li", {}, l)) ])
    こうすれば、コンパイラや新しい構文を作らなくても、ループや変数の補間などを自然に得られる。誰か説明してくれるとうれしい

    • 多くの人は JSX 版のほうがずっと読みやすく、なじみがあると感じると思う。もちろん好みの問題ではある
      公平に言えば、JSX でも通常のループや補間をそのまま使う。{} はバニラ JS の式を受け取る
      ただし、要素タグを createElement 呼び出しに展開するコンパイル段階が必要なだけ
    • 個人的には、提示されたコードを見て、それが JSX と同じくらい良いと思う人たちのことがよく理解できない
      人によって情報の処理の仕方は違うのだろうが、その例は句読点が多く、素早く把握するのが非常に難しい
      頭の中で構文を自分で分解しないと生成される DOM 構造が見えないので、コンパイラが必要かどうかよりも、DOM 構造を一目で理解できることのほうがはるかに重要だ
      ちなみに自分はニューロダイバーシティの当事者で、この説明が役に立てばと思う
    • その論理のウサギ穴を下り続けると、最終的に Common Lisp にたどり着く
    • 同じことを考えたことがある。利点の一つは HTML のように見える点だと思う
      ブラウザの開発者ツールで見るものに近いので、比較やデバッグがしやすい
    • 単一ファイルや単純なアプリなら、そのスタイルでも問題ない
      しかし複雑なアプリでコンポーネントが数百個になり、互いにインポートして使い始めると、JSX タグで書く方法がシンプルで理解しやすい構文のように感じられる
      React への反発も結局、単純なことには過剰だというところから多く出ていて、実際に過剰だ
      ただし、人によって作るアプリや複雑さ、要件は異なるので、どちらか一方がより魅力的だと単純化することはできない。異なる問題のための異なるツールだ