はじめに
JavaScriptにはWeb Componentsという機能が存在します。
標準化されたコンポーネントは、個人的に欲しかったのでよく使ってますが、
機能が不足していたり、冗長な書き回しになったり、少し不便です。
そこでWeb Componentsを使いやすくするためのプログラムを作ったので、その紹介です。
kit.jsの使い方
-
kit.js
をモジュールとして読み込む - クラスのコンストラクタ等で
kit(this)
を実行する
<script type="module">
import kit from 'https://cdn.jsdelivr.net/gh/worldace/kit/kit.js'
class SampleElement extends HTMLElement{
constructor(){
super()
kit(this) // これだけ
}
html(){
return `<h1>タイトル</h1><p>本文</p>`
}
}
customElements.define('sample-element', SampleElement)
</script>
<sample-element></sample-element>
基本的な使い方はこれだけです。
次からはkit.js
の機能について説明してきます。
HTMLの自動登録
- クラスの
html()
が返すテンプレートが、自動的にShadowDOMに登録されます - テンプレートはHTML文字列・DOM・仮想DOMの3種類に対応
テンプレートがHTML文字列の場合
class SampleElement extends HTMLElement{
html(){
return `
<h1>タイトル</h1>
<p>本文</p>
`
}
constructor(){
super()
kit(this)
}
}
テンプレートをtemplateタグに書く場合
class SampleElement extends HTMLElement{
html(){
return document.querySelector('template') // JavaScript内にHTMLを書かずに済む
}
constructor(){
super()
kit(this)
}
}
※仮想DOMについては後述
CSSの自動登録
- クラスの
css()
が返す文字列が、CSSとしてShadowDOMに登録されます - CSSファイルに書くのと同じ要領で書けます
class SampleElement extends HTMLElement{
html(){
return `<h1>タイトル</h1><p>本文</p>`
}
css(){
return `
h1{color:blue;}
p{color:red;}
`
}
constructor(){
super()
kit(this)
}
}
-
css()
の結果は再利用され、adoptedStyleSheetsに登録されるので効率的です。(Safari除く) -
html()
のテンプレート内に<style>タグを書くことも可能ですが、生成毎に処理が走るので非効率。
イベントの自動登録
クラスに$ID名_イベント名
という名前のメソッドがあると、イベントとして登録されます
class SampleElement extends HTMLElement{
constructor(){
super()
kit(this)
}
$title_click(event){ // IDが title のタグに click イベントを登録
this.$.result.textContent = 'タイトルがクリックされました'
// イベント解除例
// this.$.title.removeEventListener('click', this.$title_click)
}
html(){
return `<h1 id="title">タイトル</h1> <p id="result"></p>`
}
}
- イベント内の
this
は固定の安心設計。(発生源はevent.targetで取得) - 特別なIDが登録先として使えます。例えば"$_click"なら
shadowRoot
に登録されます
特別なID | 登録先DOM |
---|---|
$ | this.shadowRoot |
$Host | this |
$Window | window |
$Document | document |
$Body | document.body |
HTML選択の短縮化
HTMLを選択するにはquerySelector()
を使いますが、冗長なのでjQuery風の短縮構文があります。
//単数選択
this.$('セレクタ') // this.shadowRoot.querySelector('セレクタ') の短縮形
//複数選択はセレクタの頭に*を付ける。戻り値は配列
this.$('*セレクタ')
IDが付いてるタグには、さらに短くアクセスできます。
this.$.ID名 // this.shadowRoot.querySelector('#ID名') の短縮形
LightDOMでは、IDが付いてるタグにはwindow.ID名
でアクセス可能です。
既存プロパティと重複しないように、ID名を $ から始めるのがベストプラクティス。
仮想DOMの使用
テンプレートには仮想DOMを使うこともできます。使い方は
-
html()
はkit
タグ付きテンプレートか配列を返す - 描画したい時に
kit(this)
を実行する
文法はReact風で、直感的に書けるようにhtmとpreactを採用。
注意点としてタグは必ず閉じてください。単独タグでも閉じてください。<br />
仮想DOMのサンプル
カウンタ
class SampleElement extends HTMLElement{
constructor(){
super()
this.count = 0
kit(this) // 初回は全描画される
}
click(event){
this.count++
kit(this) // 2回目からは差分描画
}
html(){
return kit`<button onclick=${this.click}>+</button> <p>${this.count}</p>`
}
}
フォームに入力されたものを表示する
class SampleElement extends HTMLElement{
constructor(){
super()
this.result = '未入力'
kit(this) // 全描画
}
input(event){
this.result = event.target.value
kit(this) // 差分描画
}
html(){
return kit`<input oninput=${this.input} /> <p class=abc>${this.result}</p>`
}
}
配列を使い、リストを動的に追加する
class SampleElement extends HTMLElement{
constructor(){
super()
this.list = [Date.now()] // 現在の時間
kit(this)
}
click(event){
this.list.push(Date.now())
kit(this)
}
html(){
return kit`
<button onclick=${this.click}>追加</button>
<ol>
${this.list.map(v => kit`<li>${v}</li>`)}
</ol>
`}
}
二重ループ
class SampleElement extends HTMLElement{
constructor(){
super()
this.table = [
['バナナ', '200円'],
['リンゴ', '100円'],
]
kit(this)
}
html(){
const tr = []
for(const row of this.table){
tr.push(kit`<tr>${row.map(v => kit`<td>${v}</td>`)}</tr>`)
}
// one-liner
// this.table.map(row => kit`<tr>${row.map(v => kit`<td>${v}</td>`)}</tr>`)
return kit`<table>${tr}</table>`
}
}