テンプレート・リファレンス

lit-htmlテンプレートは、htmlタグでタグ付けされたJavaScript標準のテンプレートリテラルによって生成されます。リテラルの内容は、ほとんどがプレーンであり、ディレクティブか、HTMLです。

html`<h1>ハロー、ワールド</h1>`

バインディングやJavaScript評価式は、JavaScript標準のテンプレートリテラル構文を使います。

html`<h1>ハロー、${name}</h1>`

テンプレート構造

lit-htmlテンプレートはきちんと整えられたHTMLでなければならず、ちゃんと指定された場所でしかバインディングされません。テンプレートは値が置き換えられる前にブラウザ内部の組み込みHTMLパーサーによって解析されます。

不正な形式に対して警告されません lit-htmlではほとんどの不正な形式のテンプレートを検出できないので、期待した動作がされないテンプレートに警告が表示されることはありません。よって、正しいHTMLの構造を維持するには特別な注意が必要です。

バインドされる型

JavaScript評価式はテキストコンテンツまたは属性の値で描画されます。

バインディングにはいくつかの種類があります。

テキストのバインディングは、要素のテキストコンテンツのどこにでもつくれます。

イベントリスナー

イベントリスナーは、関数またはhandleEventメソッドを持つオブジェクトにすることができます。リスナーを指定するとaddEventListener/removeEventListenerに引数とともに渡され、capturepassiveonceなどオプションをつけることができます。

const listener = {
  handleEvent(e) {
    console.log('クリックされました');
  },
  capture: true
};

html`<button @click=${listener}>クリック</button>`

サポートされているデータ型

バインドされる形式によって異なる型がサポートされます:

テキストバインディングでサポートされている型

テキストコンテンツにおけるバインディングは、広範囲の型を使えます。

プリミティブ値: 文字列、数値、真偽値、null、undefined

プリミティブの値は、テキストコンテンツまたは属性値に使われた時に文字列に変換されます。以前の値と等しいかどうかがチェックされ、値が変更されていない場合DOMは更新されません。

TemplateResultオブジェクト

テンプレートは、JavaScript評価式の値として TemplateResultを渡すことで入れ子にすることができます:

const header = html`<h1>ヘッダー</h1>`;

const page = html`
  ${header}
  <p>これはサンプルテキストです</p>
`;

DOMノード

どんなDOMノードも指定された位置に値に渡すことができます。ノードはその時点でDOMツリーに関連付けられているため、現在の親から削除されます:

const div = document.createElement('div');
const page = html`
  ${div}
  <p>これはサンプルテキストです</p>
`;

配列もしくはイテラブル

配列とイテラブル(列挙可能なオブジェクト)もうまくサポートされています。異なる型でも混在させることができます。

const items = [1, 2, 3];
const list = () => html`items = ${items.map((i) => `item: ${i}`)}`;
const items = {
  a: 1,
  b: 23,
  c: 456,
};
const list = () => html`items = ${Object.entries(items)}`;

JavaScriptによる制御フロー

lit-htmlには組み込みの制御方法はありません。代わりに、通常のJavaScript評価式とJavaScript文を使います。

三項演算子による条件

三項演算子は、インラインで条件を追加するのに最適です:

html`
  ${user.isloggedIn
      ? html`ようこそ、 ${user.name}`
      : html`ログインしてください`
  }
`;

If文を使った条件

if文の条件をテンプレートの外部で定義し、テンプレート内で使うことができます。

getUserMessage() {
  if (user.isloggedIn) {
    return html`Welcome ${user.name}`;
  } else {
    return html`Please log in`;
  }
}

html`
  ${getUserMessage()}
`

Array.mapによるループ

リストを描画するのに、Array.mapを使ってデータをテンプレートのリストに変換できます:

html`
  <ul>
    ${items.map((i) => html`<li>${i}</li>`)}
  </ul>
`;

ループ文

const itemTemplates = [];
for (const i of items) {
  itemTemplates.push(html`<li>${i}</li>`);
}

html`
  <ul>
    ${itemTemplates}
  </ul>
`;

組み込みディレクテブ

ディレクティブは、バインディングの描画方法をカスタマイズすることで、lit-htmlを拡張できる関数です。

lit-htmlにはいくつかの組み込みディレクティブが含まれています。

ディレクティブは変更される可能性があります lit-htmlに含まれるディレクティブとAPIは、v1.0がリリースされる前に変更される可能性があります。

asyncAppend と asyncReplace

asyncAppend(asyncIterable)
asyncReplace(asyncIterable)

使用場所: テキストバインディング

JavaScriptの非同期イテレータは、データへの非同期順次アクセスのための汎用インタフェースを提供します。イテレータとよく似ていますが、コンシューマはtoを呼び出して次のデータ項目を要求しますが、非同期イテレータnext()を返し、イテレータがPromiseで準備が整ったときにアイテムを提供できるようにします。

lit-htmlは、非同期イテレータを使用するための2つのディレクティブを提供します。

新しい値を前の値の後に追加します。

前の値を新しい値に置き換えます。

例:

import {asyncReplace} from 'lit-html/directives/async-replace.js';

const wait = (t) => new Promise((resolve) => setTimeout(resolve, t));
/**
 * 1つずつ増える整数を待つ非同期イテラブルを返す
 */
async function* countUp() {
  let i = 0;
  while (true) {
    yield i++;
    await wait(1000);
  }
}

render(html`
  Count: <span>${asyncReplace(countUp())}</span>.
`, document.body);

近い将来に、ReadableStreamは非同期イテラブルになり、fetch()を直接テンプレートにストリームさせることができるようになります:

import {asyncAppend} from 'lit-html/directives/async-append.js';

// ストリーミング配信する10億桁の円周率を返すエンドポイントURL
const url =
    'https://cors-anywhere.herokuapp.com/http://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt';

const streamingResponse = (async () => {
  const response = await fetch(url);
  return response.body.getReader();
})();
render(html`π is: ${asyncAppend(streamingResponse)}`, document.body);

cache

cache(conditionalTemplate)

使用場所: テキストバインディング

テンプレートが使用されていない時は、描画されたDOMノードをテンプレート用にキャッシュします。conditionalTemplateの引数はいくつかのテンプレートのうちの1つを返すことができるJavaScript評価式です。cacheconditionalTemplateの現在の値を描画します。 テンプレートが変更されると、ディレクティブは新しい値に切り替える前に現在のDOMノードをキャッシュします。

例:

import {cache} from 'lit-html/directives/cache.js';

const detailView = (data) => html`<div>...</div>`; 
const summaryView = (data) => html`<div>...</div>`;

html`${cache(data.showDetails
  ? detailView(data) 
  : summaryView(data)
)}`

lit-htmlはテンプレートを再レンダリングするとき、変更された部分のみを更新します。必要以上にDOMを作成または削除することはありません。しかし、あるテンプレートから別のテンプレートに切り替えると、lit-htmlは古いDOMを削除して新しいDOMツリーをレンダリングする必要があります。

cacheディレクティブは与えられたバインディングと入力テンプレートに対して生成されたDOMをキャッシュします。上記の例では、 summaryViewdetailViewテンプレートの両方に対してDOMをキャッシュします。あるビューから別のビューに切り替えるとき、lit-htmlはキャッシュされたバージョンの新しいビューを入れ替えて、それを最新のデータで更新するだけです。

classMap

class=${classMap(classObj)}

使用場所: 属性バインディング (class属性を全て置き換えるものでなければなりません)

オブジェクトに基づいてクラスのリストを設定します。オブジェクト内の各キーはクラス名として扱われます。キーに関連付けられた値が真であれば、そのクラスが要素に追加されます。

let classes = { highlight: true, enabled: true, hidden: false };

html`<div class=${classMap(classes)>装飾されたテキスト</div>`;
// <div class="highlight enabled">装飾されたテキスト</div>として描画されます

class属性に対する属性バインディングでのみclassMapを使うことができ、それは属性の値全体でなければならないことに注意してください。

// このような使い方はできません
html`<div class="someClass ${classMap(moreClasses}">壊れたdiv要素</div>`;

ifDefined

ifDefined(value)

使用場所: 属性バインディング

属性を設定する場合、値が定義されていれば属性を設定し、値が undefined の場合は属性を削除します。

他のテキストコンテンツなど属性以外に使用した場合に、このディレクテブはなにもしません。

例:

import {ifDefined} from 'lit-html/directives/if-defined';

const myTemplate = () => html`
  <img src="/images/${ifDefined(image.filename)}">
`;

guard

guard(dependencies, valueFn)

使用場所: どこでも

識別されたJavaScript評価式のいずれかが一意性(identity)を変更しない限り、高価なテンプレート関数(valueFn)の再評価は避けてください。valueFnの戻り値ははキャッシュされる可能性があります。

expressions(JavaScript評価式)では単一の値(配列とならない)であり、もしくは監視する対象を含めた配列とすることができます。

このguardディレクティブは最後の既知の値をキャッシュし、プリミティブが値を変更したときやオブジェクト参照が変更されたときなど、JavaScript評価式の一意性(identity)が変更された場合にのみ再描画されます。

import {guard} from 'lit-html/directives/guard';

const template = html`
  <div>
    ${guard([immutableItems], () => immutableItems.map(item => html`${item}`))}
  </div>
`;

この場合、immutableItemsの配列の参照が変更された場合にのみ評価されます。

repeat

repeat(items, keyfn, template)
repeat(items, template)

プロミスを含む一連の値の1つをパーツ(部分)に描画します。

使用場所: テキストバインディング

イテラブル(iterable)から生成された一連の値(通常は TemplateResultsオブジェクト)を繰り返して表示し、変更されたときにそれらの項目を効率的に更新します。 keyFnが定義されていれば、必要に応じてDOMを移動させてキーとDOMの関連付けを維持するので、DOMの挿入を最小限に抑えるためにはrepeatを使うのが最も効率的です。

例:

import {repeat} from 'lit-html/directives/repeat';

const myTemplate = () => html`
  <ul>
    ${repeat(items, (i) => i.id, (i, index) => html`
      <li>${index}: ${i.name}</li>`)}
  </ul>
`;

keyFnが指定されていない場合、repeatはアイテムと値の単純なMapと同様に動作し、DOMは潜在的に異なるアイテムに対して再利用されます。

いつ repeatを使うべきか、そしていつ標準のJavaScriptフロー制御を使うべきかについての議論は repeatディレクティブを使ったテンプレートの繰り返しを見てください。

styleMap

style=${styleMap(styles)}

使用場所: 属性バインディング (style属性を全て置き換えるものでなければなりません)

styleMapディレクティブはオブジェクトに基づいて要素にスタイルを設定します。オブジェクトの各キーはスタイルプロパティとして扱われ、値はそのプロパティの値として扱われます。例えば:

let styles = { backgroundColor: 'blue', color: 'white'}'
html`<p style=${styleMap(styles}>ヘロー、スタイル!</p>`;

ダッシュを含むCSSプロパティの場合は、キャメルケースに相当するものを使用するか、プロパティ名を引用符で囲むことができます。 たとえば、CSSプロパティの font-family fontFamilyまたは 'font-family'のいずれかとして書くことができます:

{ fontFamily: 'roboto' }
{ 'font-family': 'roboto' }

styleMapディレクティブはstyle属性の値としてのみ使うことができ、その属性の値全体でなければなりません。

unsafeHTML

unsafeHTML(html)

使用場所: テキストバインディング

引数をテキストではなくHTMLとしてレンダリングします。

これは、クロスサイトスクリプティングの脆弱性を招く可能性があるため、サニタイズまたはエスケープされていないユーザーや外部から入力と一緒に使用するのは危険です。

例:

import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';

const markup = '<div>生のHTMLとして出力</div>';
const template = html`
  危険な可能性があるHTMLを出力:
  ${unsafeHTML(markup)}
`;

until

until(...values)

使用場所: どこでも

最終的なコンテンツが利用可能になるまで、プレースホルダのコンテンツを描画します。

Promiseを含む一連の値をとります。値は優先度順に表示され、最初の引数は最高の優先度を持ち、最後の引数は最低の優先度を持ちます。値がPromiseの場合、優先度の低い値は解決されるまで描画されます。

値の優先順位は、非同期データのプレースホルダコンテンツを作成するために使えます。たとえば、保留中のコンテンツを含むPromiseを最初の優先度の高い引数にすることができ、非プロンプトのローディングインジケータテンプレートを2番目の優先度の低い引数として使えます。ローディングインジケータがすぐにレンダリングされ、Promiseが解決するとプライマリコンテンツが描画されます。

例:

import {until} from 'lit-html/directives/until.js';

const content = fetch('./content.txt').then(r => r.text());

html`${until(content, html`<span>読み込み中...</span>`)}`