はじめてのLit
それでは、Litを使って、初めてのカスタム要素を作ってみましょう。公式ドキュメントにはNPMを使ってインストールする方法も解説されていますが、せっかくなので、ブラウザの import 機能(JavaScriptモジュール)を使って、Litを利用してみます。
まずは、HTMLファイルを用意します(リスト2)。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <!-- (1) --> <script type="module" src="./index.js"></script> <title>はじめてのLit</title> </head> <body> <!-- (2) --> <my-element></my-element> </body> </html>
(1)では古き良き <script> タグとは違った形でJavaScriptの読み込みを行っています。今回はNPMを使わずにJavaScriptモジュールを使用するので、 type="module" 属性を指定して、読み込んだ index.js のファイル内でJavaScriptモジュールの文法が利用できるようにしました。
また、 index.js の内部でカスタム要素を定義する予定なので、先行して(2)で <my-element> というカスタム要素のタグを実装してあります。
次は、JavaScriptファイルを用意します(リスト3)。
// (1)
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
class MyElement extends LitElement {
render() {
return html`
<h1>Hello World!</h1>
`;
}
}
customElements.define('my-element', MyElement);
大部分はリスト1で解説した通りですが、このリスト3で実際に動かすにあたって、(1)でモジュールを読み込む方法がリスト1と変わっています。NPMを前提としたプログラムでは、import文のfrom以降にはモジュール名( "lit" など)を記載しますが、JavaScriptモジュール仕様では、モジュールのURLを直接指定します。特別にインストールの作業を行う必要はないのです。
さて、実は前述の2ファイルだけで、最低限の実装は完了しています。Webサーバーに各ファイルを配置して、 index.html を表示してみます(図4)。
期待した通り、「Hello World!」が表示されましたね。DOMツリーを確認してみると、シャドウホストである <my-element> に紐づけられたシャドウルートの下に、 render() でテンプレートとして実装した <h1>Hello World!</h1> が描画されていることがわかります。
大きく手間ひまをかけずに、Litでの開発環境構築ができました。
[コラム]別バージョンのLitを利用する
本文を読んでから少し時間が経つと、リスト3とは別のバージョンの(より新しい)モジュールを利用したくなることが出てくると思います。その場合は、公式ドキュメントに記載の通り、次のページから lit-core.min.js や lit-all.min.js を探して、import文に記載してください。
lit-core.min.js には、Litでカスタム要素を成立させるための、最低限の機能( html や LitElement )のみが含まれています。一方、 lit-all.min.js には、coreの内容に加えて、テンプレート内で複雑な操作を行う際に使える便利関数(ディレクティブと呼ばれています)などが含まれます。
本記事では最低限の機能を解説しているため、 lit-core.min.js を使っていますが、次回は少し込み入った内容にも挑戦するため、 lit-all.min.js を使用する予定です。
プロパティを管理する
単純なテキストの表示ができたので、次は動的なデータを表示する方法について学んでいきましょう。LitElement で作成したカスタム要素のクラスには、表示内容を切り替えるための「プロパティ(properties)」と呼ばれるデータを定義できます。リスト4に簡単な例を挙げます。
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
class MyElement extends LitElement {
static properties = {
name: {}, // (1)
}
constructor() {
super();
this.name = "名無し"; // (2)
}
render() {
return html`
<h1>プロパティのサンプル</h1>
<p>こんにちは、${this.name}さん!</p><!-- (3) -->
`;
}
}
customElements.define('my-element', MyElement);
まずは、staticプロパティの properties の中で、(1)のように扱いたいプロパティの名前とオプションを設定します。今回は name という名前にしたので、このクラス内では this.name が更新可能なプロパティとして LitElement に認識されます。特にオプションは必要なかったので、空オブジェクト( {} )にしました。
次は(2)のコンストラクタ内で値の初期化を行います。適切な初期値を指定します。そして、(3)でテンプレート内にプロパティを埋め込みました。通常のテンプレートリテラルに値を埋め込む場合と同様に ${} で変数名を囲んでいます。
次は、リスト4をリスト5のHTMLで表示してみましょう。
<!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="./properties.js"></script> <title>Litのプロパティ</title> </head> <body> <!-- (1) --> <my-element></my-element> </body> </html>
内容としてはリスト2とほとんど同じで、ファイル名が変わった程度です。強いていえば、(1)で何も属性をつけていない点には注目しておいてください。
リスト5を表示すると、図5のような表示になります。
ちゃんと値が埋め込まれていますね。 実は、 properties に指定したプロパティは、カスタム要素の属性としても利用できます。 <my-element> に name 属性を付与してみたのがリスト6です。
<!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="./properties.js"></script> <title>Litのプロパティ</title> </head> <body> <!-- (1) --> <my-element name="田中"></my-element> </body> </html>
(1)でカスタム要素に name="田中" という属性をつけました。これを表示すると、図6のようになります。
テンプレートの ${this.name} の部分が「田中」で表示されました。このように、プロパティを定義すると外部からもパラメータとして指定できるようになります。
プロパティのオプション
properties にプロパティを定義する際に、リスト7のような形でオプションを付与することができます。
static properties = {
count: {
attribute: false,
type: Number,
},
}
指定できるオプションは表1の通りです。
| オプション名 | 概要 |
|---|---|
| attribute | プロパティをHTMLタグの属性として利用できるかどうか |
| converter | 属性から複雑な値を渡された場合にクラス内の値と相互変換するための関数 |
| hasChanged | プロパティに更新があった場合に呼び出されるコールバック |
| noAccessor | Litがスーパークラスのアクセサを上書きしないようにする |
| reflect | プロパティの更新に応じて、DOMツリーの属性情報を書き換える |
| state | クラス内でのみ利用し、属性としては利用しない |
| type | 属性で受け取った文字列型をクラス内で何型に変換するか |
これらのオプションを組み合わせることで、HTMLタグとして記述した場合に属性から受け取ったデータをどのように扱うかを調整できます。これらのオプションについては、次回、他の機能の状態管理に使いながら解説します。
イベントを扱う
LitElement で作ったカスタム要素がプロパティを持てることは分かりましたが、ある程度動きのあるUIを作ろうとすると、クリックイベントを扱ったり、イベントに応じてプロパティを更新したりしたくなりますよね。
続いては、イベントを扱う方法について解説します。簡単な例として、ボタンを押すと数字のカウントが0から一つずつ増えていくアプリケーションを用意しました(リスト8)。
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
class MyElement extends LitElement {
static properties = {
count: { type: Number, reflect: true }
}
constructor() {
super();
this.count = 0;
}
render() {
return html`
<h1>イベントのサンプル</h1>
<p>カウント: ${this.count}</p>
<!-- (1) -->
<button @click="${() => this._increment()}">+1</button>
`;
}
// (2)
_increment() {
this.count += 1;
}
}
customElements.define('my-element', MyElement);
(1)にボタンを定義しました。HTMLのクリックイベントでは click 属性を使いますが、今回は @click という見慣れない属性を使っています。これは、Litがイベントハンドリングを行うための特殊な属性です。 @click の値として関数を指定することで、クリック時に関数が呼び出されます。今回は(2)に定義した _increment() がクリックのたびに実行されます。 this.count は properties に定義済みなので、データを更新するとUIも更新されます。
リスト7を実際に動かしてみた様子が図7です。
このように、Litではインタラクションを伴う処理も比較的簡単に記述できます。
まとめ
今回はLitの動作環境の構築と、簡単な使い方について学びました。次回は、もう少し込み入った属性の使い方や、リスト表示の方法について学んでいきます。お楽しみに。
