(検討中につき随時更新していこうかと)
経緯
コンポーネント指向ってだけなら、もしかして
アレ(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" とする
<script type="module" src="./src/index.js"></script>
2. リスナ関数を持つコンポーネントは、グローバルスコープでコンポーネントを呼び出せるようにする
エントリポイントの一番最初で ComponentManager を import する。
import "./components/ComponentManager.js";
//...
window.components = new Map();
3. コンポーネントはシングルトンで実装する
※複数必要な場合は getInstance(id) などとする(例:src/components/Square.js)
class Game {
static getInstance() {
return instance;
}
//...
}
const instance = new Game();
export const { getInstance } = Game;
4. 親コンポーネントの render() のみ、あらかじめレンダリング先の HTMLElement を受け取り render() でそこに埋め込む
※ render() 再実行可
//...
(() => {
Game.getInstance().init(document.querySelector("#root")).render();
})();
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() をコールする。
class Board {
//...
render() {
return /* html */ `
<div class="board-row">
<!-- ... -->
</div>
`;
}
handleClick() {
//...
// this.render() ←NG ↓OK
Game.getInstance().render();
}
//...
}
6. イベントハンドラはHTML属性を使い、addEventListener を使わない
リスナ受け渡しのため、上記2で準備した window.components を使う
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() でコールする
class Game {
//...
init(parent) {
//...
Board.getInstance().init();
}
//...
}
課題
- 宣言的UIと言うには
${Component.getInstance().render()}
ではなく<Component />
でないといけないのかな?(記事から宣言的UIを削除) - グローバルスコープに変数を用意するのに抵抗感がある(window.components)
- HTMLのリスナ指定が冗長(window.components を window.listeners にすれば少し短くなるが)
- 子孫コンポーネントの再レンダリングでも親コンポーネントの render() を使う点、負荷や表示に問題が出そうな気も。もう少し規模の大きい実装を試してみると問題点が分かるかも
補足
トランスパイルプロセスを追加すれば Typescript もいけるかと