1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

再利用性を高めたWeb Components

Last updated at Posted at 2025-01-01

Web Componentsを使ってマイクロフロントエンドを実現する方法は多くのプロジェクトで採用されています。

私も仕事でWeb Componentsを扱っていますが、再利用性の部分で課題を感じており、その解決策をこのブログで提案したいと思います。

再利用性の課題感

例えば以下のようなコンポーネントを想像します

<h1>My Todo</h1>
<my-todos></my-todos>

スクリーンショット 2024-12-31 14.22.02.png

このmy-todosコンポーネントは、todoの一覧を表示するコンポーネントで、JavaScriptのファイルを読み込むことで、どのサイトでも簡単にこのカスタムエレメントを使用することができます。また、適切にCSSを実装することで、レイアウトも調整できます。

ただ、これは本当に再利用性が高いと言えるのでしょうか?

例えば以下のようなユースケースではどうでしょうか

  1. リストスタイルのデザインではなく、カードタイプのデザインが必要な場合
  2. リストスタイルはそのままで良いが、個人のTodoではなく、TeamのTodoを表示したい場合

これらのニーズを満たすために、my-todosコンポーネントにプロパティを追加することを考えるかもしれません。

例えば、

<!-- カードスタイルのデザインということをプロパティで伝える -->
<my-todos design="card"></my-todos>

または、

<!-- teamのtodoということをプロパティで伝える -->
<my-todos type="team"></my-todos>

これらのアプローチでは、プロパティを増やすことでユースケースに対応しようとしますが、元々のシンプルな再利用性が失われてしまうことになります。
my-todosという名前のコンポーネントで、チームのTodoを表示するという矛盾が生じてしまいます。

また、プロパティを追加することを避けて、ゼロから新しいコンポーネントを作る選択肢を取る場合、車輪の再発明が避けられず、開発が非効率になることが多いです。

解決策

そこで、今日紹介する解決策というのは、「slottodoを表示するレンダラーを注入する」というアプローチです。

この方法により、コンポーネント間の柔軟な切り替えが可能になり、再利用性を損なうことなく、異なるデザインやユースケースに対応できます。

コンポーネントの分割

まず、先ほどのmy-todosコンポーネントをどう分割するかを考えます。

my-todosコンポーネントの主な役割は「自分のTodoの一覧を取得して表示すること」です。この2つの機能を別々のコンポーネントに分けることで、役割ごとに責任を持たせることができます。

コンポーネント 役割
my-todos 自分のtodoの一覧を取得し、親コンポーネントに伝える
list-renderer 材料(todoのid一覧)を受け取り、リストスタイルでtodoを描画する

実装イメージ

Litで実装するイメージとしては以下のような感じです

親コンポーネント(呼び出し側)

親コンポーネントではイベントリスナーを設定し、my-todosからtodoのIDの一覧を受け取り、それをlist-rendererに渡します。

<my-todos  @todo-fetched=${this.handleTodoFetched}>
  <list-renderer .ids=${this.todoIds}></list-renderer>
</my-todos>

my-todosコンポーネント

my-todosコンポーネントは、fetchTodos関数を使ってTodoの一覧を取得し、そのID一覧を親コンポーネントに渡します。

constructor() {
  super()

  fetchTodos().then((data) => {
    const event = new CustomEvent('todo-fetched', {
      detail: {
        ids: data.ids,
      },
    })

    this.dispatchEvent(event)
  })
}

render() {
  return html`
    <div>
      <slot></slot>
    </div>
  `
}

list-rendererコンポーネント

受け取ったTodoのID一覧をもとに、リストスタイルでTodoを描画します。

  @property({ type: Array })
  ids = [] as number[]

  render() {
    return html`
      ${this.ids.map((id) => html` <todo-list .todoId=${id}></todo-list>`)}
    `
  }

メリット

このアプローチでは、list-rendererを例えばcard-rendererに置き換えるだけで、カードタイプのデザインに切り替えることができます。また、my-todosteam-todosに置き換えることで、チームのTodoを表示することができます。

このように、コンポーネントを単一責任で分け、差異だけを変更することで、新しいユースケースに柔軟に対応できるようになります。

結論

Web Componentsの再利用性を高めるためには、コンポーネントの責務を分け、柔軟にカスタマイズできる仕組みを提供することが重要。

slotとカスタムイベントを活用することで、再利用性を保ちながら、デザインやデータの変更にも簡単に対応できるようになります。

ソースコードなど

実装してみたサイト

ソースコード

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?