SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

再利用性とカプセル化のためのWeb Componentsを基礎から学ぶ

Web ComponentsのイベントやスロットをReactで扱う

再利用性とカプセル化のためのWeb Componentsを基礎から学ぶ 第8回

  • X ポスト
  • このエントリーをはてなブックマークに追加

 前回、Web Componentsで作成したカスタム要素へReactからデータを流し込む形で利用する方法について解説しました。今回は、カスタム要素内で発火したイベントをReactで受け取る方法と、カスタム要素内のスロットへReact由来のUIを流し込む方法について解説します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

対象読者

  • 再利用可能なHTMLやCSSを整備したいJavaScriptエンジニア
  • ReactからWeb Componentsを利用してみたいReactエンジニア

前提環境

 筆者の検証環境は以下の通りです。

  • macOS Sonoma 14.2.1
  • Google Chrome 121.0.6167.184
  • Node.js v21.6.2
  • npm v10.2.4
  • react 18.2.0
  • react-dom 18.2.0
  • lit 3.1.2
  • @lit/react 1.0.3

Web ComponentsとReactをより密接に連携させる

 前回は、Web ComponentsとReactを連携させるために、@lit/react を使って、単純にデータを外から中へと流し込む方法を解説しました。

 今回はもう少し密接な連携を目指します。Reactコンポーネントとしての自然な振る舞いを求めるのであれば、コンポーネント内部で発生したイベントを、React側で関数コールバックとして受け取ることができるようにしたいですよね。また、他のコンポーネントの外側に被せることで意味を持つタイプのコンポーネントであれば、Reactの children と同じように、子要素を扱えると嬉しいです。@lit/react を使って、これらの挙動を実現する方法を解説していきます。

 動作確認の環境については、前回のものをそのまま踏襲するので、セットアップ方法については前回の記事をご覧ください。

Web Components内で発火したイベントをReactで受け取る

 イベントのコールバックをReactで受け取る方法を見てみましょう。といっても、@lit/react の力を持ってしても、Reactから受け取った関数コールバックをカスタム要素の内部まで届けることはできません。カスタム要素の内部でイベントを発火させて、それをReactで受け取るという方法をとります。前回のリスト4を少し改造する形で、イベントを発火させる実装を追加したのがリスト1です。

[リスト1]src/event-handling/my-evented-counter-element.js
import {LitElement, html} from 'lit';

export class MyEventedCounterElement extends LitElement {
  static properties = {
    count: { type: Number }
  }

  constructor() {
    super();
    this.count = 0;
  }

  render() {
    return html`
      <p>カウント: ${this.count}</p>
      <button @click="${() => this.increment()}">+1</button>
    `;
  }

  increment() {
    this.count += 1;
    // (1)
    this.dispatchEvent(new CustomEvent(
      'changed',
      {
        bubbles: true, // (3)
        composed: true, // (4)
        detail: { count: this.count } // (2)
      }
    ));
  }
}

customElements.define('my-evented-counter-element', MyEventedCounterElement);

 (1)の dispatchEvent で、changed という名前のCustomEventを発火させています。外部に現在のカウントを伝えたいので、(2)のようにカウント数を detail に入れているのは理解しやすいと思います。(3)のbubbles と(4)の composed は、イベントの伝播に関する設定です。bubbles をtrueにすると、イベントがDOMツリーを遡って祖先に伝播します。composed をtrueにすると、シャドウDOMの境界を越えてイベントが伝播します。つまり、両方をtrueにしておくと、シャドウDOMの内側から発火したイベントが、シャドウDOMの外側にあるDOMツリーを遡って、Reactの管理下まで伝播するようになります。

 では、リスト1のカスタム要素から発火したイベントを、Reactで受け取ってみましょう。リスト2のように、@lit/react を使ってReactコンポーネントを作成します。

[リスト2]src/event-handling/EventHandler.jsx
import React, { useState } from 'react';
import { createComponent } from '@lit/react';
import { MyEventedCounterElement } from './my-evented-counter-element';

const MyEventedCounterElementComponent = createComponent({
  tagName: 'my-evented-counter-element',
  elementClass: MyEventedCounterElement,
  react: React,
  events: {
    onChanged: 'changed', // (1)
  }
})

export const EventHandler = () => {
  const [count, setCount] = useState(0);

  const isOdd = Math.abs(count % 2) === 1;

  return (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <MyEventedCounterElementComponent
        onChanged={(ev) => { // (2)
          setCount(ev.detail.count); // (3)
        }}
      />
      <p>
        {count}は{isOdd ? '奇数' : '偶数'}です。{/* (4) */}
      </p>
    </div>
  )
}

 実際に動かしてみましょう。ブラウザで http://localhost:5173/ を開くと、図1の通り表示されます。

図1:イベントを受け取った例
図1:イベントを受け取った例

 カスタム要素内のカウントをReact側で受け取り、奇数か偶数かの判定と表示をReact側で行っています。

 リスト2で特に注目したいのは、(1)の events です。ここには、カスタム要素から発火されるイベント名と、React側で受け取る際の名前を指定します。(1)のように設定すると、カスタム要素内で changed という名前のイベントが発火されたときに、React側で onChanged という名前のコールバックが呼ばれるようになります。実際に呼ばれると、(2)の引数には dispatchEvent() で発火した CustomEvent が渡されるので、(3)のように detail からカウント数を取り出すことができています。

 明示的に dispatchEvent() を呼び出したり、イベント名を指定したりと、少し手間がかかりますが、カスタム要素内で発火したイベントをReact側で受け取ることができました。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
Web Components内のスロットにReact由来のUIを流し込む

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
再利用性とカプセル化のためのWeb Componentsを基礎から学ぶ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19264 2024/04/16 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング