LoginSignup
0
0

More than 1 year has passed since last update.

ライブラリ・バンドラ・Web Componentsを使わずコンポーネント指向開発(Singleton Components v2)

Last updated at Posted at 2022-08-03

(検討中につき随時更新していこうかと)

経緯

コンポーネント指向ってだけなら、もしかして
アレ(React)もコレ(Web Components)も必要ないんじゃ、、、と。

static render() 版の末尾で検討を続けているもので
https://qiita.com/_tt/items/18f682304e66a6cb8521
現状までを整理し実装してみました。

サンプル実装

React チュートリアルを Singleton Components v2 で実装してみた
https://codesandbox.io/s/youthful-fire-kelbs7?file=/index.html

以下はこのサンプルを元にしています

方法

1. コンポーネント単位でファイル分割するため、エントリポイントを type="module" とする
index.html
<script type="module" src="./src/index.js"></script>
2. リスナ関数を持つコンポーネントは、グローバルスコープでコンポーネントを呼び出せるようにする

エントリポイントの一番最初で ComponentManager を import する。

src/index.js
import "./components/ComponentManager.js";
//...
src/components/ComponentManager.js
window.components = new Map();
3. コンポーネントはシングルトンで実装する

※複数必要な場合は getInstance(id) などとする(例:src/components/Square.js)

src/components/Game.js
class Game {
  static getInstance() {
    return instance;
  }
  //...
}
const instance = new Game();
export const { getInstance } = Game;
4. 親コンポーネントの render() のみ、あらかじめレンダリング先の HTMLElement を受け取り render() でそこに埋め込む

※ render() 再実行

src/index.js
//...
(() => {
  Game.getInstance().init(document.querySelector("#root")).render();
})();
src/components/Game.js
class Game {
  //...
  render() {
    this._parent.innerHTML = /* html */ `
      <div class="status">
        <!-- ... -->
      </div>
    `;
  }

  constructor() {
    this._parent = null;
    //...
  }
  init(parent) {
    this._parent = parent;
    //...
  }
  //...
}
5. 子孫コンポーネントの render() はHTML文字列を return し、上位コンポーネントの render() 内で埋め込んでもらう。

子孫コンポーネントの render() は再実行不可、再実行したい場合は最上位コンポーネントの render() をコールする。

src/components/Board.js
class Board {
  //...
  render() {
    return /* html */ `
      <div class="board-row">
        <!-- ... -->
      </div>
    `;
  }
  handleClick() {
    //...
    // this.render() ←NG ↓OK
    Game.getInstance().render();
  }
  //...
}
6. イベントハンドラはHTML属性を使い、addEventListener を使わない

リスナ受け渡しのため、上記2で準備した window.components を使う

src/components/Square.js
class Square {
  //...
  render() {
    return /* html */ `
      <button
        class="square"
        onclick="window.components.get('${Square.name}').getInstance().handleClick()"
      >
        ${this._player}
      </button>
    `;
  }
  //...
  init() {
    window.components.set(Square.name, Square);
  }

  handleClick() {
    //...
  }
  //...
}
7. コンポーネントの初期化は init() で行い、子コンポーネントの init() は親コンポーネントの init() でコールする
src/components/Game.js
class Game {
  //...
  init(parent) {
    //...
    Board.getInstance().init();
  }
  //...
}

課題

  1. 宣言的UIと言うには${Component.getInstance().render()}ではなく<Component />でないといけないのかな?(記事から宣言的UIを削除)
  2. グローバルスコープに変数を用意するのに抵抗感がある(window.components)
  3. HTMLのリスナ指定が冗長(window.components を window.listeners にすれば少し短くなるが)
  4. 子孫コンポーネントの再レンダリングでも親コンポーネントの render() を使う点、負荷や表示に問題が出そうな気も。もう少し規模の大きい実装を試してみると問題点が分かるかも

補足

トランスパイルプロセスを追加すれば Typescript もいけるかと

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0