前回、lit-htmlを勉強したので、LitElementについて勉強します。 Polymerとはいろいろ変ったらしく、TypeScriptで作られていますね、、、
import {LitElement, html} from '@polymer/lit-element'
class MyElement extends LitElement {
static get properties() {
return {
name: { type: String }
}
}
constructor(){
super()
this.name = 'World'
}
render() {
return html`
<h1>Hello ${this.name} !</h1>
<input @input=${ e => this.name=e.target.value }>
`
}
}
customElements.define('my-element', MyElement)
こんな感じで書いたHello World !(23行)が、
import {LitElement, customElement, property, html} from '@polymer/lit-element'
@customElement('my-element') class extends LitElement {
@property() name = 'World'
render(){
return html`
<h1>Hello ${this.name} !</h1>
<input .value=${this.name} @input=${ e => this.name=e.target.value }>
`
}
}
これぐらい(14行)で書けるならTypeScriptもよいのかも
※ 厳密には
@customElement
デコレータではHTMLElementTagNameMap
インタフェースを定義する必要があるらしい
LitElementとは
- サイズが小さく高速
- 主としてHTML属性(
attribute
)やDOMプロパティ(property
)の操作を便利にしてくれるHTML要素の拡張ライブラリ - 描画はlit-htmlによって非同期に処理されるので、関数的に作らなくてはいけない
内部的なライフサイクル
基本、プロパティを操作(例: this.foo = 'a'
)してlit-htmlに渡せばよいのですが、細かく制御するには下記(Element update lifecycle)の遷移を把握しておく必要があります( await this.updateComplete
とかよく使うぽい )。
At a high level, the update lifecycle is:
1. A property is set.
2. The property’s hasChanged function evaluates whether the property has changed.
3. If the property has changed, requestUpdate fires, scheduling an update.
4. shouldUpdate checks whether the update should proceed.
5. If the update should proceed, the update function reflects the element’s properties to its attributes.
6. The lit-html render function renders DOM changes.
7. The updated function is called, exposing the properties that changed.
8. The updateComplete Promise resolves. Any code waiting for it can now run.
更新のライフサイクルについて(意訳):
- プロパティが設定されると、
-
プロパティ毎に設定 される
hasChanged(newValue, oldValue)
によって プロパティが変更されたかどうか を確認。 - プロパティが変更されたなら
requestUpdate()
を呼んで描画をスケジューリングし、 -
shouldUpdate(changedProperties)
によって描画をすべきか確認。 - 描画してよいなら
update(changedProperties)
が呼ばれて各属性やプロパティを更新し、 - (やっと) lit-htmlがDOMに描画。
-
(※追加 最初の描画時は
firstUpdated(changedProperties)
呼ばれつつ)update(changedProperties)
が呼ばれ、プロパティも更新済みとなり、 -
updateComplete
がresolveされる
Todoリスト
awesome-lit-htmlに載っていた、LitElementとvueやReactの実装を比較した素敵な記事があったので、これをTSで短く書き換えてみます。
import {LitElement, html, property, customElement} from '@polymer/lit-element'
@customElement('todo-item') default class extends LitElement {
@property() todo=''
@property({type: Function}) remove
render() {
return html`${this.todo} <button @click=${this.remove}>-</button>`
}
}
@customElement('todo-list') default class extends LitElement {
@property() list = ['clean the house','buy milk']
@property() todo = ''
newTodo(){
this.list = [...this.list, this.todo]
this.todo=''
}
render() {
return html`
<h2>ToDo List</h2>
<ul>
${this.list.map( (v, index) => html`<li>
<todo-item
.todo=${v}
.remove=${() => this.list = this.list.filter((_,i)=> index !== i)}></todo-item>
</li>`)}
</ul>
<input
.value=${this.todo}
@input=${e=>this.todo = e.target.value}
@keypress=${e=> e.target.value !== '' && e.key === 'Enter' && this.newTodo()}>
<button @click=${e=>this.todo !== '' && this.newTodo()}>+</button>
`
}
}
親要素(todo-list
)でToDoの各アイテムを削除する関数をつくって、
<todo-item .remove=${() => this.list = this.list.filter((_,i)=> index !== i)}>
子要素(todo-item
)でプロパティとして受けとるのが新鮮でした(他フレームワークでは一般的なのかな、、、)
@property({type: Function}) remove
他
(未解決課題: IE11でエラー。CSSの適用)
サンプルのWebアプリでは別途Reduxなどで状態管理をして、pwa-helperを使ってPWAにしてるので、次回また勉強してみます。