Lit入門 コンポーネント作成編
Lit入門 コンポーネント作成編
はじめに
前回の記事の続きです、この記事では下記の前提条件で解説を進めていきます。
- npmの基本的な利用方法
- Typescriptの基礎的な知識
- Classに関しての基礎知識
コンポーネントの構成
まずは、シンプルなコンポーネントからコードを確認していきましょう
LitではコンポーネントをClassとして設計していきます。
本来のWebComponentsもこのようにClassとしてカスタム要素を定義します。
import { LitElement, css, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('my-element')
export default class MyElement extends LitElement {
static styles = css`
h1 { color: red; }
`
@property()
name: string = 'world';
render() {
return html`
<h1>hello ${this.name}</h1>
`;
}
}
基本的にはClassとしてカスタム要素を定義していきます。
こちらについては通常のWebComponentsと同じですが、異なる点としては下記の2点です。
-
HTMLElement
ではなくLitElement
を継承している点 -
@customElement()
デコレーターを使用している点
各項目の解説
では、コードの上から順にどのような役割を担っているのか確認していきます。
カスタム要素の宣言
まずは@customElement()
デコレーターを使用してクラスを作成します、継承するクラスはHTMLElement
ではなくLitElement
をインポートして継承します。
@customElement()
の引数に要素名を渡し、クラス名には要素名をアッパーキャメルケースにしたものを設定します。
@customElement("my-element")
export default class MyElement extends LitElement {
//...
}
スタイル定義
次の行ではクラスの静的フィールドとしてカスタム要素に適用されるCSSが記述されています。
カスタム要素はへのスタイルの適用はcss
関数を利用してstyles
フィールドに追加します。
このstyles
フィールドへの追加は関数の返り値であるCSSResult
もしくはその配列で行えます。
static styles = css`
/* この中にCSSを記述することでスタイルを定義できる */
h1 { color: red;}
`;
また、CSSを外部ファイルとして分割したい場合はunsafeCSS
関数を利用します。
CSSを外部ファイル化する場合の手順
-
raw-loader
やAsset Modules
などを利用してCSSを文字列として読み込める環境を作成する。 - 適当な名前でCSSをインポートする。
-
css
関数をunsafeCSS
関数に変更し引数に 2. でインポートした文字列を渡す。
import { LitElement, unsafeCSS, html } from "lit";
import style from "./style.css";
@customElement("my-element")
export default class MyElement extends LitElement {
static styles = unsafeCSS(style);
// ...
}
プロパティの定義
次の項目では@property()
デコレーターを使用してプロパティの定義を行っています。
名前の通りプロパティはVue.jsやReactのprops
に近い存在ですので、他のフレームワークを利用したことがある方はそれを頭に入れると分かりやすいかと思います。
デフォルトの動作としては、宣言したプロパティはカスタム要素上で属性値として操作可能で、プロパティの変化を検知してプロパティを使用している項目が自動的にアップデートされるリアクティブな値として利用できるようになります。
@customElement('my-element')
export default class MyElement extends LitElement {
// ...
@property()
name: string = 'world';
}
として定義されたプロパティは
<my-element name="hi"></my-element>
のように属性として指定可能になります。
また、通常の要素と同じようにDOMとしても取得・変更が行えます。
const myElement = document.querySelector("my-element");
console.log(myElement.name); // world
myElement.name = "hi";
console.log(myElement.name); // hi
propertyデコレーターのオプション
propertyデコレーターには引数としていくつかのオプションをを設定することができます。
簡単な解説のため使用頻度が高そうなオプションをいくつか解説していきます。
attribute
attribute
オプションはおそらく一番使用頻度が高いと思われます。
boolean | string
が指定可能で初期値はtrue
です。
@property({attribute: 'some-property'})
someProperty: string = "";
初期値attribute: true;
の場合someProperty
として宣言した値はsomeproperty
という属性値になってしまいます。
通常HTMLの属性値はケバブケースで表されるためattribute: 'some-property';
として属性値の値を明示的に指定する必要があります。
If the value is false, the property is not added to observedAttributes.
公式ドキュメントには上記のように書いていますがfalse
にした場合は同名の属性値は監視対象から外れ、HTML側から設定を行っても内部的な更新は行われなくなります。
これに関しては実際に挙動を確認してみると理解が早いと思います。
reflect
reflect
オプションは内部的にプロパティの値が変わった場合の属性値の扱いを変更します。
boolean
が指定可能で、初期値はfalse
です。
@property({reflect: true})
name = "";
単純に設定を行っただけでは効果が分かりづらいですが、reflect: true
を設定した場合は内部的なプロパティの更新に合わせてHTMLの属性値も自動的に更新されるようになります。
このオプションはattribute
や後述するconverter
が設定に従って属性値の更新を行います。
実例としてはchecked
属性のようにユーザーのアクションに応じて属性値が変更が変わる仕様を模倣したい場合に使用する場合が多いと感じます。
attribute
及び reflect
の挙動についての図
type
type
オプションはTypeScriptを使用している場合に非常に役立ちます。
通常getAttribute()
などで取得された属性値はstrign
型として受け取りますが、type
オプションを設定することで自動的に型変換を行ってくれます。
String | Number | Boolean | Array | Object
が指定可能で、初期値はString
です。
Number
を指定した場合はNumber()
関数によって変換されます。
Boolean
を指定した場合はchecked
属性のように指定した属性値が存在すればtrue
を存在しなければfalse
に変換されます。
Array
とObject
を指定した場合はどちらも同じくJSON.parse()
を使用して変換が行われます。
@property({type: Array})
someProps = [];
変換方法をカスタマイズしたい場合はconverter
オプションを設定することで変換方法を自分で定義できます。
renderメソッド
最後にrenderメソッドですが、このメソッドの返り値をhtml
関数を使用してテンプレートを定義できます。
Vue.jsで言えば<template>
タグ、Reactの関数コンポーネントの返り値に近い存在でしょうか。
@customElement('my-element')
export default class MyElement extends LitElement {
// ...
render() {
return html`
<h1>hello ${this.name}</h1>
`;
}
}
renderメソッド内部に直接処理を書くこともできるので、下記のようにテンプレートの分割や動的な出し分けなども可能です。
@customElement('my-element')
export default class MyElement extends LitElement {
@state()
isHeading = false;
// ...
render() {
// 通常のメソッドと同じように内部で変数の定義や使用が可能
const heading = html`<h1>hello ${this.name}</h1>`;
const paragraph = html`<p>hello ${this.name}</p>`;
// プロパティやステートを参照して要素の出し分けなども可能
return html`
<div>
${this.isHeading ? heading : paragraph}
</div>
`;
}
}
まとめ
ざっくりとした解説になってしまいましたが、最低限コンポーネントを作るための方法を解説させていただきました。
実際に機能を持ったコンポーネントを作成する場合にはイベントの定義や各種デコレーターについての知識が必要になりますが文章量が多くなってしまうためこの記事はここで終了とさせていただきます。
次回(があれば)イベントの定義か紹介しきれなかった各種デコレーターについてなど解説しようと思います。
参考ドキュメント
Discussion