ベースを作成する
まずはベースとなるHTMLとカスタム要素のクラスを作成しておきましょう。まずはHTMLです(リスト1)。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script type="module" src="./todolist.js"></script> <title>Litで作るToDoリスト</title> </head> <body> <todo-list></todo-list> </body> </html>
HTML側では todolist.js を読み込んで <todo-list> を使用していますね。こちらの定義を todolist.js に作成します(リスト2)。
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/all/lit-all.min.js';
class ToDoListElement extends LitElement {
static properties = {};
render() {
return html`
<h1>ToDoリスト</h1>
<li><label><input type="checkbox" />タスク1</label></li>
<li><label><input type="checkbox" />タスク2</label></li>
<li><label><input type="checkbox" />タスク3</label></li>
`;
}
}
customElements.define('todo-list', ToDoListElement);
イメージを掴みやすくするために、チェックリストを仮置きしました。
これを元に、ToDoリストを作っていきます。
タスクのリストを表示する
まずは、配列データを元にチェックリストを生成できるようにしましょう(リスト3)。
// (略)
class ToDoListElement extends LitElement {
static properties = {
tasks: {
state: true, // (1)
},
};
constructor() {
super();
// (2)
this.tasks = [
{ id: 1, label: 'カプセル化と再利用性について学ぶ', completed: true },
{ id: 2, label: 'Web ComponentsのAPIを学ぶ', completed: true },
{ id: 3, label: 'Litの基本的なAPIを学ぶ', completed: true },
{ id: 4, label: 'Litの実践的な使い方を学ぶ', completed: false },
];
}
render() {
return html`
<h1>ToDoリスト</h1>
${this.tasks.map((task) => { // (3)
return html`
<li>
<label>
<input type="checkbox" ?checked=${task.completed} /><!-- (4) -->
${task.label}
</label>
</li>
`;
})}
`;
}
}
まず、配列データはクラスの内部プロパティ(属性として公開しないプロパティ)として tasks という名前で定義したいので、(1)で tasks の宣言と、 state: true オプションの設定を行いました。次に、コンストラクタのタイミングで、(2)のように配列データを初期化します。そして、データの用意ができたら、 render() 内のテンプレートに反映していきます。最も簡単な方法は、(3)のように Array.map() 関数でデータをテンプレートの配列に変換することです。これで配列データをテンプレート内に並べられます。最後に、データ内の completed プロパティを使ってチェック状態を設定したいので、(4)のように ?checked=${task.completed} でチェックボックスのchecked属性に値を設定しています。
リスト3を表示すると、図3のようになります。
tasks に定義したデータのうち、 completed: true になっているものだけにチェックがついています。どうやら上手くいったようです。
?checked の先頭に ? の文字がついているのが気になりますね。これはLit特有のテンプレート記法の一つで、boolean型の属性を記述するときに属性名の頭に付けるプレフィックスです。他にも、カスタム要素のプロパティにアクセスするためのプレフィックスとして . があったり( .value=${value} のように使用)、イベントリスナーを登録するためのプレフィックスとして @ があります( @click=${() => this.onClick()} のように使用)。
ディレクティブを用いて短く書く
リストを表示する際、 Array.map() を使う方法でも問題ないのですが、テンプレート内にJavaScriptを書きすぎると文書構造の見通しが悪くなるという問題があります。そのため、Litはテンプレート内で頻出する各種処理を簡潔に記述するための、ディレクティブという関数群を用意しています。
例えば、ループや条件分岐に関するディレクティブとしては表1のようなものがあります。
| ディレクティブ名 | 概要 |
|---|---|
| when | 第一引数のtruthy/falthyに応じて表示を切り替える(三項演算子の代替) |
| choose | 条件にマッチしたものを表示する(switch-case文の代替) |
| map | Iterableなデータをリストに変換する(for-of文の代替) |
| repeat | mapとほぼ同じ機能で、より強力にデータとDOMを紐付けて並び替えに強くする |
| join | テンプレートのリストの間に別のテンプレートを挿入する( Array.join() の代替) |
| range | 特定の範囲の整数の配列を生成する |
| ifDefined | 属性の定義時に用い、引数のデータがnullかundefinedの場合に属性の定義自体を削除する |
render() メソッド内がごちゃごちゃしそうな頻出処理がスッキリしそうなものばかりです。
今回のサンプルでは map() を使ってみましょう(リスト4)。
// (1) mapを追加
import {LitElement, html, map} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/all/lit-all.min.js';
// 略
render() {
return html`
<h1>ToDoリスト</h1>
<!-- (2) mapに置き換え -->
${map(this.tasks, (task) => {
return html`
<li>
<label>
<input type="checkbox" ?checked=${task.completed} />
${task.label}
</label>
</li>
`;
})}
`;
}
// 略
map() を使用するには、(1)のようにLitライブラリからインポートします。使い方は(2)のように第一引数にIterableオブジェクト、第二引数にテンプレートを記述します。この例では Array.map() とさほど変わりませんが、ディレクティブの map() は第一引数に配列以外のIterable型オブジェクトも取れるので、幅広いデータで活用できます。
