10 ポイント 投稿者 GN⁺ 2025-04-23 | 3件のコメント | WhatsAppで共有
  • Writing JavaScript Views the Hard Way : フレームワークなしで純粋なJavaScriptだけを使ってビューを構築する方法を説明した記事
  • 直接的な命令型アプローチによって、パフォーマンス、保守性、移植性を確保
  • 状態更新とDOM更新を明確に分離し、それぞれの役割に応じて厳格な命名規則と構造的パターンに従う
  • この方式はデバッグしやすくすべてのブラウザ互換性を保証し、0 dependenciesという大きな利点がある
  • 初心者には難しいかもしれないが、学習することで実際のシステムの動作方式に対する深い理解を得られる

JavaScriptビューを「Hard Way」で書く

これは何か?

  • この方式は、React、Vue、lit-htmlのようなフレームワークなしにJavaScriptだけでビューを構成するパターン
  • 特定のライブラリやツールではなくコーディングパターンそのものであり、スパゲッティコードの問題を防ぐ
  • 直接的な命令型方式を使うことで、抽象化を減らし直感性を高める

フレームワークと比べた利点

  • パフォーマンス: 命令型コードのため不要な演算なしに動作し、ホットパスとコールドパスの両方に適している
  • 0 dependencies: ライブラリアップグレードや互換性問題から自由
  • 移植性: 書いたコードはどのフレームワークにも移植可能
  • 保守性: 明確なセクション構造と命名規則により、コードの位置を把握しやすい
  • ブラウザ対応: IE9以上のほとんどのブラウザと互換性があり、IE6までも一部修正で対応可能
  • デバッグ容易性: 中間レイヤーなしで浅いスタックトレースを提供
  • 関数型構造: 不変ではないが、すべての構成要素が関数ベースで構成される

構造の説明

全体構造

  • templateclone()init() 関数で構成される
  • init() 関数は、状態変数、DOM参照、更新関数、イベントリスナーなどを含む1つのビューインスタンスを生成する

コード構造の例 (Hello World)

const template = document.createElement('template');  
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;  
  
function clone() {  
  return document.importNode(template.content, true);  
}  
  
function init() {  
  let frag = clone();  
  let nameNode = frag.querySelector('#name');  
  let name;  
  
  function setNameNode(value) {  
    nameNode.textContent = value;  
  }  
  
  function setName(value) {  
    if(name !== value) {  
      name = value;  
      setNameNode(value);  
    }  
  }  
  
  function update(data = {}) {  
    if(data.name) setName(data.name);  
    return frag;  
  }  
  
  return update;  
}  

init() 関数の内部構成

1. DOM変数

  • fragclone() から生成されたテンプレート断片
  • 内部要素は querySelector() で参照し、変数名は fooNode 形式を使う

2. DOMビュー

  • 別のビューを含む部分(再利用可能なサブビュー)
  • 例:
let updateChildView = childView();  
  • ビュー更新関数は updateFoo 形式で命名

3. 状態変数

  • ビュー内で変更されうるデータ値
  • DOM更新を効率化するため、現在値と比較して必要なときだけDOMを変更する

4. DOM更新関数

  • DOM要素の状態を変更するときに使う
  • 例:
function setNameNode(value) {  
  nameNode.textContent = value;  
}  
  • DOM操作は必ずこの関数の中でのみ行う

5. 状態更新関数

  • 状態変更ロジックと、それに伴うDOM反映を含む
  • 変更されていない値は無視して不要なDOM変更を防止
  • 例:
function setName(value) {  
  if(name !== value) {  
    name = value;  
    setNameNode(value);  
  }  
}  

templateclone() 関数

template

  • <template> 要素で静的なHTML構造を生成
  • DOMに直接挿入されず、cloneによって複製を生成する

clone()

  • document.importNode(template.content, true) で複製
  • 必要に応じて .firstElementChild を使い、ルート要素を返せる

相互作用の方式

親 → 子のデータフロー

  • 親は子の init() を呼び出して更新関数を取得し、update({ name: 'foo' }) 形式で呼び出す

イベントベースのデータ伝播

  • 基本的にprops down, events upモデルに従う
  • 下位ビューはイベントを上位へディスパッチして通信する

Reactとの比較

  • constructor() (React)init() (Hard Way)
    • コンポーネントの初期設定を担当する
  • render() (React)update(data) (Hard Way)
    • 画面更新とUI更新の役割を担う
  • this.setState() (React)setX(value) (Hard Way)
    • 状態値を直接設定する方式に置き換えられる
  • props (React)update(data) で渡された値 (Hard Way)
    • 親コンポーネントから渡されたデータを処理する方式
  • JSX / Virtual DOM (React)HTMLテンプレート + DOM API (Hard Way)
    • 宣言的UIの代わりに、手動のDOM操作とテンプレートを使う

結論

  • この方式は、慣れたフレームワークに比べて初期参入障壁は高いが、次のような強みがある:
    • パフォーマンス最適化
    • 完全な制御権
    • 学習による深い理解
  • 役割ごとの関数分離と命名規則によって、フレームワークなしでも保守可能なUIを構成できる

互換性

  • 最新の例はモダンブラウザ向けAPIを使っているが、IE9以下までも関数ベースの代替によって対応可能
  • イベントの代わりにpropsとして関数を渡す方式を使えば、IE6まで拡張可能

3件のコメント

 
wfedev 2025-04-24

結局はWeb Componentsへ……

 
ahwjdekf 2025-04-23

おめでとうございます。またひとつ、jsフレームワークが生まれました。

 
GN⁺ 2025-04-23
Hacker Newsのコメント
  • 多くのJS開発者にとっては異端かもしれないが、state変数はアンチパターンだと思う

    • Web Componentsを使う場合、"平坦な"変数型のためにstate変数を追加する代わりに、DOM要素のvalue/textContent/checkedなどを唯一の信頼できる情報源として使う
    • 必要ならsetterとgetterを追加する
    • コード量が少なくても、自然に多くのことが正しく動く
    • WebComponentsを使うと、オブジェクトと隣接するHTMLテンプレートが分離され、スパゲッティコードではなくフジッリやマカロニのような細かな分割になる
  • このアプローチは非常に保守しやすいと記事では説明されているが、同意しない

    • デザインパターンが慣習のみに基づいている
    • 複雑なアプリで複数の開発者が同時に作業すると、少なくとも1人は慣習から外れる可能性が高い
    • iOSのUIKitのようなクラスベースのUIフレームワークは、すべての開発者に標準APIセットの使用を強制するため、コードが予測可能で保守しやすい
  • 最近、viteと一緒に純粋な"バニラ" TypeScriptでアプリケーションを書いていて、フロントエンドの"最高"の実践についてますます疑問を持つようになっている

    • スケーラビリティについてはまだ結論を出せないが、性能面では大きな利点がある
    • 楽しいし、多くを学べるし、デバッグは簡単で、アーキテクチャも理解しやすい
    • 一番恋しいのはテンプレート機能
  • このアプローチは古いbackbone jsライブラリを思い出させる

    • Webプラットフォームに適応したMVCパターンの例を示すGitHubリポジトリもある
  • 最近、似たようなものを思いついたが、template要素は使っていない

    • 関数とテンプレートリテラルを使って文字列を返し、既存要素のinnerHTMLに入れたり、新しいdiv要素を作ってそこに入れたりしている
    • 関数がネストして、合理的な方法で構成するのが難しい
  • このコードは、リアクティブなビューライブラリが置き換えようとしている手動更新コードとまったく同じに見える

  • 20年近くプログラミングをしてきたが、フロントエンドフレームワークにはどうしても慣れない

    • バックエンドのほうが得意なので、セキュリティ関連のやり取りはサーバーを経由すべきだと考えている
    • JSは、堅牢なHTMLとCSSの基盤にクライアントサイド機能を追加するものだと見ている
  • React.createElementに似たヘルパーを使っている

    • モックサーバーダッシュボードの動作例がある
  • HTMLベースのツール向けJSツールキットを構築しようとして、deja-vu.junglecoder.comで作業中

    • 適切なリアクティブ/双方向データバインディングはまだ解決できていないが、grab/patchはかなり良い
    • テンプレートを使うやり方だと、テンプレートの一部を移動させるのがとても簡単
  • 大学卒業後の最初の正式な職場で、DelphiソフトウェアのWeb版を作る仕事をしていた

    • チームはすでにフロントエンドを3回目の書き直し中で、フレームワークを変更しなければならなかった
    • 独自フレームワークを書くべきだと主張したが、チームは私の提案を気に入らなかった
    • その後、別の会社からもっと良い提案を受けて去った
    • その後、tiny.jsという別のフレームワークも試し、個人プロジェクトで使っている