LitElement + TypeScript でTodoリスト

![lit-capture](https://qiita-image-store.s3.amazonaws.com/0/90678/0ec6e704-863a-df04-8888-bdecaed13e53.png)

[前回](https://qiita.com/howking/items/644b75c021a718c23d9a)、[lit-html](https://lit-html.polymer-project.org/)を勉強したので、[LitElement](https://lit-element.polymer-project.org/)について勉強します。<img src="https://www.polymer-project.org/images/logos/p-logo.png" width="37" height="26"> [Polymer](https://www.polymer-project.org/)とはいろいろ[変った](https://43081j.com/2018/08/future-of-polymer)らしく、[TypeScript](https://www.typescriptlang.org/)で作られていますね、、、

```js:my-element.js
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行**)が、

```ts:my-element.ts
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 }>
    `
  }
  
}
```

[:globe_with_meridians: デモサイト (stackblitz)](https://stackblitz.com/edit/lit-element-hello-world-71a1gg?file=my-element.ts)

これぐらい(**14行**)で書けるならTypeScriptもよいのかも

> ※ 厳密には`@customElement`デコレータでは`HTMLElementTagNameMap`インタフェースを[定義](https://github.com/Polymer/lit-element/blob/ffb3306f8f0dc15ae1e930a3d8df2baf47918b5b/src/lib/decorators.ts#L36)する必要があるらしい

## LitElementとは

- サイズが小さく高速
- 主としてHTML属性(`attribute`)やDOMプロパティ(`property`)の操作を便利にしてくれるHTML要素の拡張ライブラリ
- 描画はlit-htmlによって非同期に処理されるので、関数的に作らなくてはいけない

## 内部的なライフサイクル

基本、プロパティを操作(例: `this.foo = 'a'`)してlit-htmlに渡せばよいのですが、細かく制御するには下記([Element update lifecycle](https://lit-element.polymer-project.org/docs/lifecycle/about#overview))の遷移を把握しておく必要があります( `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.
```

更新のライフサイクルについて(意訳)：

1. プロパティが設定されると、
2. **プロパティ毎に設定** される `hasChanged(newValue, oldValue)`によって **プロパティが変更されたかどうか** を確認。
3. プロパティが変更されたなら`requestUpdate()`を呼んで描画をスケジューリングし、
4. `shouldUpdate(changedProperties)` によって描画をすべきか確認。
5. 描画してよいなら`update(changedProperties)`が呼ばれて各属性やプロパティを更新し、
6. (やっと) lit-htmlがDOMに描画。
7. **(※[追加](https://github.com/Polymer/lit-element#advanced-update-lifecycle)** 最初の描画時は`firstUpdated(changedProperties)`呼ばれつつ) `update(changedProperties)`が呼ばれ、プロパティも更新済みとなり、
8. `updateComplete`がresolveされる

## Todoリスト

[awesome-lit-html](https://github.com/web-padawan/awesome-lit-html/blob/master/README.md)に載っていた、LitElementと[vue](https://jp.vuejs.org/)や[React](https://reactjs.org/)の実装を比較した素敵な[記事](https://medium.com/@westbrook/litelement-to-do-app-1e08a31707a4)があったので、これをTSで短く書き換えてみます。
 
```ts:todo-list.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>
      `
  }
}
```

[:globe_with_meridians: デモサイト (stackblitz)](https://stackblitz.com/edit/lit-elemet-todo-list-n98ndke?file=todo-list.ts)

親要素(`todo-list`)でToDoの各アイテムを削除する関数をつくって、

```js:todo-list
<todo-item .remove=${() => this.list = this.list.filter((_,i)=> index !== i)}>
```

子要素(`todo-item`)でプロパティとして受けとるのが新鮮でした(他フレームワークでは一般的なのかな、、、)

```js:todo-item
@property({type: Function}) remove
```

## 他

(未解決課題: IE11でエラー。CSSの適用)

サンプルの[Webアプリ](https://pwa-starter-kit.polymer-project.org/sample-apps/)では別途[Redux](https://redux.js.org/)などで状態管理をして、[pwa-helper](https://github.com/Polymer/pwa-helpers)を使ってPWAにしてるので、次回また勉強してみます。

(続き :arrow_right: [LitElement+Reduxでカウンター](https://qiita.com/howking/items/e07523a9ef0ce2817e35))