Litで作ったコンポーネントをReactで使う

## この記事の概要

UIライブラリーをWeb Componentsで作って、ReactでもVueでもSvelteでも、色々な場所で使えたら嬉しくないですか？
私は結構夢がある話だと思っています。

というわけでその素振りをしてみました。
ユーザー規模的にReactを題材にしていますが大筋は他のフレームワークでも変わらないはずです。

## Litとは？

Litとは、Web Componentsを構築するためのライブラリです。
通常のWeb Componentsは記述量が多いのと書き方が若干特殊に見えるので、Litが上手くラップして書きやすくしてくれます。

https://lit.dev/

## Lit環境（export側）の構築

まずはLit側の準備です。
公式のスターターキットがJavaScriptとTypeScript用それぞれ用意されているのですが、ちょろっと試すには重厚な印象でした。

https://github.com/lit/lit-element-starter-js

https://github.com/lit/lit-element-starter-ts

というわけでViteから作成します。

```shell
$ yarn create vite
# Lit + TypeScriptの設定を選択
$ yarn
$ yarn dev
```

初期画面が出ました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/d600c79f-c229-4ae5-8b4f-b1af98a4d2e5.png)

## Storybookの導入

今回は軽いテストですが、コンポーネントの確認用にStorybookは用意しておきます。
Viteで作った環境なので、StorybookもあわせてビルダーをViteにします。

```shell
npx storybook init --builder @storybook/builder-vite
```

これで諸々の依存関係が追加されます。

```shell
yarn storybook
```

Storybookのinitをしたことがある人にはお馴染みのコンポーネント達が表示されました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/ca67451d-b636-b84a-abe5-d5a4487fe958.png)

ただ、これで生成されるコードはイマイチです。
Shadow DOMやConstructed Stylesheetが適用されていなくてメリットが活かしきれていません。
分かりやすく`src/stories`ごと削除してしまいます。

```shell
rm -rf src/stories
```

## 新しいコンポーネントの作成

とりあえずボタンを作り直してみます。

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

type Priority = 'primary' | 'secondary'

@customElement('my-button') // 1
export class MyButton extends LitElement {

  @property() priority: Priority = 'primary' // 2

  render() {
    return html`
      <button class=${this.priority}><slot></slot></button> // 3
    `
  }

  static styles = css` // 4
    button {
      // 省略
    }
    .primary {
      // 省略
    }
    .secondary {
      // 省略
    }
  `
}
```

重要そうな場所をピックアップして説明します。

### 1. Decoratorでのカスタム要素の定義

`@customElement`によってカスタム要素を定義しています。
Decoratorを使わない場合は`customElements.define('my-button', MyButton);`の記載になります。

### 2&3. Decoratorでのプロパティの定義とslotでの要素の受け取り

このコードは、Reactで表現すると以下のような感じです。

```tsx
const MyButton = ({ priority = 'primary' }: { priority: Priority }) => {
  return (
    <button className={priority}>{children}</button>
  )
}
```

`@property`は先ほど説明したのと似ています。
これを使わないと以下のようになります。

```javascript
static properties = {
  priority: {},
};

constructor() {
  super();
  this.priority = 'primary';
}
```

また、`slot`によって`children`っぽいことができます。
（厳密には違いますが、イメージとしてはそこまで相違ないと思っています。）

### 4. Constructed Stylesheetの生成

ハッシュによるスコープどころではない強力さです。
タグセレクターなどで指定されても上書きされないため、ほとんど無敵です。

### 補足

詳細は公式ドキュメントにも載っています。

https://lit.dev/docs/getting-started/

## Storybookでの確認

見た目が良い感じになりつつ、ちゃんとカスタム要素（`my-button`）や`slot`などが適用されています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/77891970-45ee-d137-ff94-b4dcfa16e75e.png)

## ビルド

設定は省略します。
設定を変えたい場合は`vite.config.js`を触ればOKです。

```shell
yarn build
```

## React環境（import側）の構築

こちらも、設定が簡単なのでViteで作ります。

```shell
$ yarn create vite
# React + TypeScriptの設定を選択
$ yarn
$ yarn dev
```

まずは立ち上がりました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/e953cdad-2f10-1406-7534-2136c3e3589e.png)

## 依存関係の追加とReact用コードに変換

Lit製のコンポーネントをReactに変換するためにインストールします

```shell
yarn add @lit-labs/react
```

新規に`MyButton.tsx`を作成します。
ここのコードは割と見たままです。

```tsx:MyButton.tsx
import * as React from 'react'
import { createComponent } from '@lit-labs/react'
import { MyButton as MyButtonWC } from 'qiita-lit'

export const MyButton = createComponent({
  tagName: 'my-button',
  elementClass: MyButtonWC,
  react: React,
})
```

## 変換したコンポーネントの使用

普通のReactと同様に`import { MyButton } from './MyButton'`からの`<MyButton>`の使用でOKです。
コードとUIの両方が上手く見えていませんが、React側でも先ほどと同じようにWeb Componentsとして出力されています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/e14dd089-188f-9ed4-6c2a-368a839779b0.png)

設定していた`priority`のプロパティを変えてもちゃんと反映されます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214677/65f5a2f6-091f-9789-b036-22bffcc7e5af.png)

React内でWeb Componentsを使おうとpropsやイベント周りが大変になるそうです。
それらがLitによって楽に扱えるようになるのは非常に嬉しいですね。

## 最後に

ある程度長く運用しているサービス群だと、色々なフレームワークを使っていることも多いのではないでしょうか。
サービスAはReact、サービスBはVueなど……。

どうしてもそういうのが起きがちな世界において、Web Componentsで基底ライブラリーを作っておければ乗り換えやリプレイスのコストも割と下げられる気がします。
そういった検討をする際にこの記事が役立っていれば幸いです。

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox

Devトークでのお話してくださる方も募集中です！

https://jobs.qiita.com/employers/qiita-inc/dev_talks/489