【必読】なぜ今、Aura ではなく LWR を選ぶべきなのか?

【紅茶ECサイト 制作日誌 Vol.3】「カートに入れる」ボタンをどう実装する?Lightning Message Service (LMS) でコンポーネント間通信を実現する

LWRで構築する英国紅茶ブランドサイト 「The Royal Brew」 制作日誌、第3弾です。

前回は、Custom LWCで「究極の商品詳細ページ」を作り上げました。

見た目は完璧です。しかし、ECサイトとして決定的に足りないものがあります。 そう、「カート機能」 です。

画面中央にある [ADD TO CART] ボタンを押しても、現在は何も起きません。 理想は、ボタンを押した瞬間に、ヘッダー右上の「カートアイコン」の数字がポコッと増える ことです。

しかし、ここで技術的な壁にぶつかります。 「商品詳細コンポーネント」と「ヘッダーのカートアイコン」は、DOMツリー上で親子関係になく、赤の他人同士です。どうやって連携させればよいのでしょうか?

今回は、Salesforceの標準メッセージング技術 「Lightning Message Service (LMS)」 を使って、この問題を鮮やかに解決します。

DXforce Point for Developers

今回の記事で作成したコンポーネントの全ソースコードをGitHubで公開しています。 ぜひ Clone して、あなたの組織で動かしてみてください。

今回のミッション:離れたコンポーネントを会話させる

  • 送信側 (Publisher): 商品詳細ページ (Royal Product Detail)。「カートに追加」ボタンが押されたらメッセージを飛ばす。
  • 受信側 (Subscriber): ヘッダー (Royal Cart Icon)。メッセージを受け取ったらバッジの数字を増やす。
  • 放送局 (Channel): Lightning Message Channel。メッセージの通り道。

Step 1: 「放送局」を用意する (Message Channel)

まずは、会話のための専用回線(チャネル)を定義します。 これは LWC (JavaScript) ではなく、XMLファイルとして作成します。

ファイル: force-app/main/default/messageChannels/RoyalCartChannel.messageChannel-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>RoyalCartChannel</masterLabel>
    <isExposed>true</isExposed>
    <description>商品がカートに追加されたことを通知するチャネル</description>
    
    <lightningMessageFields>
        <fieldName>productId</fieldName>
        <description>追加された商品のレコードID</description>
    </lightningMessageFields>
    <lightningMessageFields>
        <fieldName>productName</fieldName>
        <description>追加された商品の名前</description>
    </lightningMessageFields>
</LightningMessageChannel>

これを組織にデプロイすることで、Salesforce全体で使える「放送局」が開設されます。

Step 2: 「送信機」を作る (Product Detail)

前回作成した商品詳細コンポーネント royalProductDetail.js に、メッセージ送信機能を組み込みます。

ポイントは publish メソッドです。

// royalProductDetail.js
import { LightningElement, api, wire } from 'lwc';
// ... (既存のimport)

// ★ LMS関連モジュールのインポート
import { publish, MessageContext } from 'lightning/messageService';
import CART_CHANNEL from '@salesforce/messageChannel/RoyalCartChannel__c';

export default class RoyalProductDetail extends LightningElement {
    // ...

    // ★ LMSのコンテキストを取得(これがないと送れません)
    @wire(MessageContext)
    messageContext;

    // ボタンが押された時の処理
    handleAddToCart() {
        const payload = {
            productId: this.recordId,
            productName: this.name
        };

        // メッセージ発射! (Fire!)
        publish(this.messageContext, CART_CHANNEL, payload);
        
        console.log('Published Add to Cart:', payload);
    }
}

HTML側でボタンに onclick={handleAddToCart} を設定すれば、送信側の準備は完了です。

DXforce Point for Developers

【技術コラム】実務では「裏側のロジック」をどう組む?

今回のコードでは、LMSの挙動を理解するために「メッセージ送信」のみを行っていますが、実際のECサイト構築では、同時にデータベースへの保存が必要です。

ボタンを押した瞬間の正しい処理フローは以下のようになります。

  1. Apexメソッドを呼び出す: 商品IDと数量をサーバーに送信し、CartItem(またはカスタムカートオブジェクト)を作成・更新する。
  2. 成功レスポンスを受け取る: サーバーから「保存完了」の合図を待つ。
  3. LMSを送信する: 保存が確定して初めて、ヘッダーに「更新して!」とメッセージを送る。

このように、「サーバー処理(Apex)→ クライアント間通信(LMS)」 という順序を守ることで、データの整合性が取れた堅牢なアプリケーションになります。今回はLMSの紹介にフォーカスするため、このサーバー処理部分は省略しています。

Step 3: 「受信機」を作る (Cart Icon)

次に、メッセージを受け取るための新しいLWC、Royal Cart Icon を作成し、サイトのヘッダー部分に配置します。

ここでは subscribe メソッドを使って、チャネルを常時監視します。

クリックしてLWCのコードを展開する

royalCartIcon.js

import { LightningElement, wire } from 'lwc';
import { subscribe, MessageContext } from 'lightning/messageService';
import CART_CHANNEL from '@salesforce/messageChannel/RoyalCartChannel__c'

export default class RoyalCartIcon extends LightningElement {
    cartCount = 0; // カートの中身の数
    subscription = null;

    @wire(MessageContext)
    messageContext;

    // コンポーネントが配置されたら購読開始
    connectedCallback() {
        this.subscribeToMessageChannel();
    }

    subscribeToMessageChannel() {
        if (!this.subscription) {
            this.subscription = subscribe(
                this.messageContext,
                CART_CHANNEL,
                (message) => this.handleMessage(message)
            );
        }
    }

    // メッセージが届いた時の処理
    handleMessage(message) {
        console.log('Received:', message);
        // カウントを増やす
        this.cartCount++;
        
        // ここで「ポコッ」と動くCSSアニメーションなどを発火させると最高です
    }
}

royalCartIcon.html

<template>
    <div class="cart-container">
        <svg class="cart-icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M19 6h-2c0-2.8-2.2-5-5-5S7 3.2 7 6H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-7-3c1.7 0 3 1.3 3 3H9c0-1.7 1.3-3 3-3zm7 17H5V8h14v12zm-7-8c-1.7 0-3-1.3-3-3H7c0 2.8 2.2 5 5 5s5-2.2 5-5h-2c0 1.7-1.3 3-3 3z" fill="currentColor"></path>
        </svg>
        
        <div class="cart-badge" lwc:if={cartCount}>{cartCount}</div>
    </div>
</template>

royalCartIcon.css

.cart-container {
    position: relative;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--royal-navy, #0F1F3D);
    cursor: pointer;
}

/* ホバー時にゴールドに変化 */
.cart-container:hover .cart-icon {
    fill: var(--royal-gold, #C5A059);
}

.cart-badge {
    position: absolute;
    top: -2px;
    right: -2px;
    background-color: var(--royal-gold, #C5A059);
    color: #fff;
    font-size: 0.75rem;
    font-weight: bold;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    /* 数字が増えた時にポンと弾むアニメーション */
    animation: pop 0.3s ease-out;
}

@keyframes pop {
    0% { transform: scale(0); }
    80% { transform: scale(1.2); }
    100% { transform: scale(1); }
}

royalCartIcon.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>65.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Royal Cart Icon</masterLabel>
    <targets>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
</LightningComponentBundle>

完成! SPAのような滑らかな連携

これらを実装してデプロイし、エクスペリエンスビルダーで配置します。 プレビュー画面で [ADD TO CART] ボタンを押してみてください。

ページのリロードなしで、ヘッダーの数字が即座に反応します。 これこそが、LWRサイト(SPA: Single Page Application)ならではのユーザー体験です。

今回のまとめ

  • DOMが離れていても大丈夫: LMSを使えば、どこにあるコンポーネント同士でも会話できる。
  • 疎結合な設計: 送信側は「誰が聞いているか」を知る必要がなく、受信側も「誰が送ったか」を知る必要がない。これにより、コンポーネントの独立性が保たれる。

次回の制作日誌

次回は、画面遷移の仕上げです。 パンくずリストや「HOMEに戻る」リンクを、正しくLWRの作法で実装する方法(NavigationMixinの落とし穴)について解説します。

DXforceの管理人

福島 瑛二

2013年にJavaエンジニアとしてのキャリアをスタート。2019年にSalesforceと出会い、Salesforceエンジニアの道へ。

デザインや UI/UX の観点からもシステムを捉え、ユーザーにとって心地よい体験を実装することにやりがいを感じています。

CRM(顧客データ)や Data Cloud と連携した高度なサイトを目に見える形で表現できる Experience Cloud に大きな可能性を見出しており、バックエンドのデータ構造とフロントエンドの表現力を極めることがこれからの Salesforce エンジニアに求められるスキルだと確信しています。

Trailblazer: efukushima

福島 瑛二をフォローする

読者の声

タイトルとURLをコピーしました