Zenn
🕌

Reactユーザー向けLit入門

2025/03/23に公開
4

React に慣れている開発者が、初めてLitに触れるときのための入門記事です。この記事では、React との違いを意識しながら、Lit の基本的な使い方を解説します。
https://lit.dev/docs/

Litとは

Litは Google が開発している軽量なライブラリで、Web Components を効率的に構築するためのライブラリです。Reactのようなライブラリではなく、純粋な Web 標準の上に構築された、軽くて高速な UI ライブラリです。

Web Componentsとは

Web Components は、ブラウザが標準でサポートしているコンポーネント技術の総称です。以下の3つの技術で構成されます:

  • Custom Elements<my-button> のような独自要素を定義可能
  • Shadow DOM:スタイルとDOMをカプセル化(外部CSSの影響を受けない)
  • HTML Templates:再利用可能なHTMLのスニペットを定義

これらを組み合わせることで、Reactなどの特定のライブラリに依存しない再利用可能なUI部品を作成できます。

Web Componentsについては、以下の記事にて大変丁寧に紹介されています

https://zenn.dev/takumaru/articles/a0be18e5def1aa

Litのメリット / 主な用途

メリット

  • 再利用性:Web標準ベースなので、どのフレームワークとも共存可能
  • 高速な描画:差分更新による効率的なDOM更新
  • TypeScriptサポートが強力

主な用途

  • ライブラリやフレームワークに依存しない UIコンポーネントライブラリの構築
    • Litで用意したコンポーネントをReactから読み込めるようにすることもできるため、共通的なコンポーネントとしてライブラリ・フレームワークを跨いで参照可能にできる
  • デザインシステムとの親和性が高い(StorybookやCode Connectに対応している)

一方で、LitはReactで構築するようなダイナミックなSPAアプリには向いていません。これは、Litは状態管理やルーティングなどの「アプリ全体を構成する機能」がReactなどに比べて弱いためです。アプリケーションの構築ではなく、それに利用するUIコンポーネントライブラリの構築に向いています。

Litが採用されている事例

  • Google Material Web
  • Adobe Spectrum Web Component

環境構築

Litの環境構築を行います。やり方はいくつかありますが、今回は比較的容易に行うことができるViteを利用した方法をとります。
https://vite.dev/guide/

まず、任意のディレクトリにて以下を実行します。今回はTypescriptを利用して構築します。

npm create vite@latest lit-ts-sample -- --template lit-ts

完了すると、以下のような構成のテンプレートが生成されます(一部抜粋)

.
├── node_modules
├── public
├── package.json
├── tsconfig.json
├── index.html
└── src
    ├── assets
    ├── my-element.ts
    ├── index.css
    └── vite-env.d.ts

src/my-element.ts が、サンプルで用意されているLitベースのコンポーネントです。内部的には、カウンターのコンポーネントが実装されています。
そちらを、index.htmlで読み込み、描画していることが確認できます。

<my-element>
   <h1>Vite + Lit</h1>
</my-element>

次に以下を実行し、ローカルサーバーを立ち上げます。

npm install
npm run dev

以下のような画面が開いたら完了です。簡単ですね。

基本構文

それでは早速基本的な構文を紹介します。Reactと比較しながら、Buttonコンポーネントを作る想定で進めます。

コンポーネントの定義

Litではクラスを用いてコンポーネント定義を行います。

React の例


export const MyButton = () => {
  return ...
}

Lit の例

import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-button')
class MyButton extends LitElement {
  render() {
    return ...
  }
}
  • @customElement('my-button') により <my-button> タグを独自のタグとして定義し、利用可能にする

テンプレート構文

Litでは、html テンプレートタグを用いて表示するHTMLを表現できます。ReactのJSXに相当します

React (JSX)

return <button className="primary">{children}</button>;

Lit (html テンプレートタグ)

//Slotで表現する場合
return html`<button class="primary">
            <slot></slot>
            </button>`;

//Propertyとして受け取ってそれを表現する場合
return html`<button class="primary">
               ${this.label}
            </button>`;
  • JSX では className、Lit では class
  • ${} を使って文字列や値を埋め込む
  • html は Tagged Template Literal(特殊なテンプレート文字列)
  • タグ利用時に囲まれた部分を描画する方法として、slotタグを利用する
    • <my-button>OK</my-button>OKの部分が描画される

Propsの渡し方

Lit の @property() デコレータは、コンポーネントのプロパティを外部から渡せるようにするための仕組みです。React でいう「props」に相当します。

React

// 親
<MyButton size="large">OK<MyButton>

// 子

type Props = {
  size: "small" | "medium" | "large"
  children: ReactNode;
}

export const MyButton:FC<Props> = ({size = "medium" , children}) => {
  return <button class={size}>{children}</button>;
}

Lit

// 親
<my-button size="large">OK</my-button>

// 子
@customElement('my-button')
export class MyButton extends LitElement {
  @property({ type: String }) size: 'small' | 'medium' | 'large' = 'medium';

  render() {
    return html`
      <button class="${this.size}">
        <slot></slot>
      </button>
    `;
  }
}
  • @propertyデコレーターを利用して、コンポーネントが受け取る値を制御する

@property の基本補足

@property({ type: String }) size: 'small' | 'medium' | 'large' = 'medium';
  • 主なオプション
オプション名 説明
type 属性値を JS の型に変換(String, Number, Boolean, Object など)
attribute 属性名をカスタマイズできる(デフォルトはプロパティ名と同じ)
reflect プロパティの変更を HTML 属性にも反映する(双方向)
  • 例: disabled を reflect させたいとき
@property({ type: Boolean, reflect: true }) disabled = false;

また、このdisabledをHTMLタグに付与する際、以下のように記述します

  render() {
    return html`
      <button class="${this.size}" ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `;
  }

?disabled という形式で指定していますが、この ? はBoolean属性であることをLitに伝える記法です。例えば、this.disabledがtrueの場合は <button disabled> として描画され、falseの場合は <button> のように描画されます。該当のBoolean属性を削除するような役割を担います。

State管理

内部で状態を持ちたい場合は @state() を使います。React の useState に相当します。

React の場合

export const MyButton:FC<Props> = ({children}) => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{children}</button>;
}

Lit の例

@customElement('my-button')
export class MyButton extends LitElement {
  @state() count = 0;
  render() {
    html`
      <button @click=${() => this.count++}>
        <slot></slot>
      </button>
    `;
  }
}
  • @state() は「内部専用の状態」で、外部から渡せない
  • 状態が変化すると自動で render() が再実行されて UI 更新される

また@stateの話とはズレますが、Litでイベントハンドラを利用する場合は、@{event name}のような形で指定します。
Reactで、onClickと指定する部分がLitだと@clickになります。

スタイリング(CSSの使い方)

ReactではCSS ModuleやStyled Componentなど幅広いスタイル方法があります。Litではコンポーネントに閉じたCSSを記述するためのcssメソッドが提供されています

import { css } from 'lit';

static styles = css`
  button {
    background: blue;
    color: white;
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  button:hover {
    background: darkblue;
  }
`;

そして、これらの構文を組み合わせて、以下のButtonコンポーネントを作ることができます

import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-button')
export class MyButton extends LitElement {
  @property({ type: String }) size: 'small' | 'medium' | 'large' = 'medium';
  @property({ type: Boolean, reflect: true }) disabled = false;

  static styles = css`
    button {
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-weight: bold;
    }

    button.small {
      padding: 4px 8px;
      font-size: 12px;
    }

    button.medium {
      padding: 8px 16px;
      font-size: 14px;
    }

    button.large {
      padding: 12px 24px;
      font-size: 16px;
    }

    button:disabled {
      background-color: #ccc;
      color: #666;
      cursor: not-allowed;
    }
  `;

  render() {
    return html`
      <button class="${this.size}" ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `;
  }
}

利用例

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Lit + TS</title>
    <script type="module" src="/src/my-button.ts"></script>
  </head>
  <body>
    <my-button>OK</my-button>
  </body>
</html>

ライフサイクル

LitにもReactのようなライフサイクルごとのメソッドがあります。

https://lit.dev/docs/components/lifecycle/

Reactとの比較表

よく使うライフサイクルのメソッドをピックアップして紹介します

タイミング React Lit
renderの処理 コンポーネント直下に記述 render()
初回描画後の処理 useEffect(() => {...}, []) firstUpdated()
描画後に副作用を実行 useEffect(() => {...}, [deps]) updated(changedProps)

例:updated()の使い方

updated(changedProps: Map<string, unknown>) {
  if (changedProps.has('value')) {
    console.log('valueが更新されました:', this.value);
  }
}

パフォーマンス

Litの大きな特徴の一つが、仮想DOMを使わずに高速な描画ができることです。
これはLit のテンプレート システム「lit-html」を利用し、動的部分の差分を直接DOMに反映しているためです。

この技術により、Reactよりも高いパフォーマンスでの描画が実現できることが公式ドキュメントに記載されています

Lit のテンプレート システム「lit-html」を React の VDOM と比較した公開ベンチマークでは、lit-html は React と比べ最低でも 8~10% 速く、一般的なユースケースでは 50% 以上速いという結果が出ています。
LitElement(Lit コンポーネントのベースクラス)により lit-html に最小限のオーバーヘッドが追加されますが、メモリ使用量、インタラクション、起動時間などのコンポーネントの機能を比較すると React のパフォーマンスを 16~30% 上回ります。

https://codelabs.developers.google.com/codelabs/lit-2-for-react-devs?hl=ja#1

まとめ

LitはReactとは思想が異なりますが、再利用性や軽量さを活かして、既存システムへのUI追加やデザインシステムに最適です。今回紹介できなかった部分などより詳細を知りたい方は、公式ドキュメントにて解説されていますので、そちらをご覧ください

https://codelabs.developers.google.com/codelabs/lit-2-for-react-devs?hl=ja#0
https://lit.dev/

4
NCDCエンジニアブログ

Discussion

ログインするとコメントできます