2022.09.20

Lightning Web Componentのデコレータの動きを見てみよう

Just a moment... (26039)

目次

はじめに

今年は新人メンターを担当しています。 新人さんからの質問に答える中で、自分でもちゃんと理解していなかったことがわかったり、新しい発見があったりと、かなり勉強になっています。名古屋の新人さん達、ありがとう!!

そんな中でLightning Web Component(以下、LWC)のデコレータの質問を受けました。ざっくり概要を説明はしたものの、実際自分で動かしてみたほうがより理解できるかと思いサンプルを作成してみました。是非自分でコードを書いて試してみてください。

前提条件

手前味噌で恐縮ですが、弊社の新人研修はかなり充実しております。 研修を終えた時点でSalesforceの基本は理解しており、LWCも作成できるようになっています。 この記事では、ブラウザでJavascriptのコンソール出力を見ることができ、以下のTrailheadモジュールの内容を理解していることを前提で説明しております。

3つのデコレータ

デコレータの説明はSalesforceの公式やこのTechBlogのほかの記事で説明されていますので、そちらをご覧ください。 今回の記事は、それらの記事を読んだあとに「自分でやってみよう!」と思った方向けに書いています。

@apiと@trackのリアクティブ/再レンダリングは、以下のTechBlogで詳しく紹介されています。

事前準備

取引先(Account)のLightning Experience レコードページに配置できるLWCを作成します。コンポーネント名は"account"にしています。表示確認用に、account.htmlに"Sample"と入れています。

<template>
        Sample
</template>
account.html
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>
account.js-meta.xml

デプロイしたら、取引先のLightning Experience レコードページに配置します。

@wireデコレータ

@wireデコレータは、ワイヤアダプタまたは Apex メソッドを指定するときに使います。

今回は「ワイヤサービスについて」の「@wire での関数のデコレート」にあるコードを利用して動きを見てみます。

上記ページでの説明の中に、「リアクティブ変数が変更されると、ワイヤサービスが新しいデータをプロビジョニングします。」とあるので、これを確認するために、 console.log を5か所追加しています。

account.html の「c-error-panel」は作成していませんので、エラーメッセージを表示するように変更しています。合わせて account.jsも21行目の this.error = ~ を変更しています。 まずは以下のサンプル通りに実装してみてください。

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class Account extends LightningElement {
        @api recordId; 

        record;
        error;

        @wire(getRecord, { recordId:  '$recordId', fields: [ 'Account.Name' ] })
        wiredRecord({ error, data }) {
                console.log('getRecord, recordId=', this.recordId);
                if (data) {
                        console.log('getRecord');
                        console.log(JSON.stringify(data));
                        this.record = data;
                        this.error = undefined;
                } else if (error) {
                        console.error('getRecord');
                        console.log(JSON.stringify(error));
                        this.error = error.body.message;
                        this.record = undefined;
                }
        }

        get name() {
                return this.record.fields.Name.value;
        }
}
account.js
<template>
        <lightning-card title="Wire Function" icon-name="standard:account">
                <template if:true={record}>
                        <div class="slds-m-around_medium">
                                <p>{name}</p>
                                <p>aId:{aid}</p>
                        </div>
                </template>
                <template if:true={error}>
                        <!-- <c-error-panel errors={error}></c-error-panel> -->
                        {error}
                </template>
        </lightning-card>
</template>
account.html

実装したらデプロイし、任意の取引先レコードを開いて確認してみます。 コンソール出力していますので、ブラウザでコンソールを表示しておきます。

コンソールに出力された内容を見ていきます。

コンソール出力の①から、recordIdに値がセットされる前にgetRecordが実行されていることがわかります。 次行の②は再度getRecordが実行されたときの出力なので、①のgetRecordはerrorもdataもなく終了しています。

②の出力ではrecordIdに値がセットされてるのでgetRecordでデータを取得できたようで、次の行にif (data)の場合のconsole.log('getRecord')が出力されています。

①と②から、コンポーネントが読み込まれた時(recordIdは空)にgetRecordが実行され、その後recordIdに値がセットされると再度getRecordが実行されていたようです。 これで、リアクティブ変数(=recordId)が変更されると、ワイヤサービスが新しいデータをプロビジョニング(=getRecordを実行)してくれたことを確認できました。

③は、getRecordした結果のdataの中身です。 dataの詳細は、getRecord の「戻り値」の 「data」に記載されている レコードで確認できます。

errorの中身も見てみたい場合は、fields: ['Account.Name']fields: ['Account.Name0']に変更しでデプロイし、Accountの画面を再読み込みしてみてください。存在しない項目を参照しようとしているのでエラーが発生します。

@apiデコレータ

@apiデコレータは、プロパティやメソッドを公開するときに使います。 今回はプロパティの公開を確認します。

公開プロパティの確認なので、公開する側とそれを利用する側の2つのコンポーネントを用意します。

@wireの確認で作成した取引先(Account)の情報を表示するコンポーネントを「公開する」側として、「利用する」側として取引先責任者(Contact)の情報を表示するコンポーネント(コンポーネント名:contact)を作成します。 取引先責任者レコードから取引先IDを取得し、取得した取引先IDを取引先の情報を表示するコンポーネントで公開されているプロパティに渡して取引先の情報を表示してみます。

作成するコンポーネントは取引先の情報を表示するコンポーネントとほぼ同じで、contact.jsはgetRecordの引数のfieldsの中身を変更してget accountId()を追加、.htmlはaccountIdの表示を追加しました。

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class Contact extends LightningElement {
        @api recordId;
        record;
        error;


        @wire(getRecord, { recordId:  '$recordId', fields: [ 'Contact.Name', 'Contact.AccountId'] })
        wiredRecord({ error, data }) {
                console.log('getRecord, recordId=', this.recordId);
                if (data) {
                        console.log('getRecord');
                        console.log(JSON.stringify(data));
                        this.record = data;
                        this.error = undefined;
                } else if (error) {
                        console.error('getRecord');
                        console.log(JSON.stringify(error));
                        this.error = error.body.message;
                        this.record = undefined;
                }
        }
        
        get name() {
                return this.record.fields.Name.value;
        }

        get accountId() {
                return this.record.fields.AccountId.value;
        }
}
contact.js
<template>
        <lightning-card title="Contact" icon-name="standard:contact">
                <template if:true={record}>
                        <div class="slds-m-around_medium">
                                <p>{name}</p>
                                <p>{accountId}</p>
                        </div>
                </template>
                <template if:true={error}>
                        {error}
                </template>
        </lightning-card>
        </template>
</template>
contact.html
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>
contact.js-meta

デプロイしたら、取引先責任者のLightning Experience レコードページに配置します。

contact.htmlに取引先(Account)の情報を表示するコンポーネントを追加します。 account.jsでrecordIdに@apiがついているので公開されており、contact.hmtlで、accountのrecordIdにcontactのaccountIdをセットできます。

<template>
        <lightning-card title="Contact" icon-name="standard:contact">
                <template if:true={record}>
                        <div class="slds-m-around_medium">
                                <p>{name}</p>
                                <p>{accountId}</p>
                        </div>
                </template>
                <template if:true={error}>
                        {error}
                </template>
        </lightning-card>

        <!-- ↓ これを追加  -->
        <template if:true={record}>
                <c-account
                        record-id={accountId}>
                </c-account>
        </template>
</template>
contact.html

デプロイして、取引先が設定されている取引先責任者レコードを開いて、取引先の情報が表示されているか確認します。

緑下線のcontactの出力にあるAccountIdのvalueが、青下線のaccountのrecordIdにセットされています。 公開プロパティにちゃんと値を渡せていることを確認できました。

次に、@apiがついていないとどうなるか確認してみます。ついでに、recordId以外の変数名でも値を渡せるのか確認します。(フローなどでrecordIdは自動でレコード ID を受け取るので、ここでも自動でなにかしら受け取っているのでは?という疑念を払うために確認してみました。)

まず、account.jsに@apiなしのaccountIdと@apiありのaIdを追加し、各変数の値を画面で確認で表示するようaccount.htmlを編集します。

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class Account extends LightningElement {
        @api recordId; 
        // ↓ここに追加
        accountId;
        @api aId;

        record;
        error;

        @wire(getRecord, { recordId:  '$recordId', fields: [ 'Account.Name' ] })
        wiredRecord({ error, data }) {
                console.log('getRecord, recordId=', this.recordId);
                if (data) {
                        console.log('getRecord');
                        console.log(JSON.stringify(data));
                        this.record = data;
                        this.error = undefined;
                } else if (error) {
                        console.error('getRecord');
                        console.log(JSON.stringify(error));
                        this.error = error.body.message;
                        this.record = undefined;
                }
        }

        get name() {
                return this.record.fields.Name.value;
        }
}
account.js
<template>
        <lightning-card title="Wire Function" icon-name="standard:account">
                <template if:true={record}>
                        <div class="slds-m-around_medium">
                                <p>{name}</p>
                                <!-- ↓ここに追加 -->
                                <p>recordId:{recordId}</p>
                                <p>accountId:{accountId}</p>
                                <p>aId:{aId}</p>
                        </div>
                </template>
                <template if:true={error}>
                        <!-- <c-error-panel errors={error}></c-error-panel> -->
                        {error}
                </template>
        </lightning-card>
</template>
account.html

accountをデプロイします。

次に、contact.htmlを変更します。 <c-account>でaccount-ida-idにもaccoutIdをセットします。

<template>
        <lightning-card title="Contact" icon-name="standard:contact">
                <template if:true={record}>
                        <div class="slds-m-around_medium">
                                <p>{name}</p>
                                <p>{accountId}</p>
                        </div>
                </template>
                <template if:true={error}>
                        {error}
                </template>
        </lightning-card>

        <template if:true={record}>
                <!-- ↓このタグに追加 -->
                <c-account
                        record-id={accountId}
                        account-id={accountId}
                        a-id={accountId}>
                </c-account>
        </template>
</template>
contact.html

contactもデプロイし、取引先が設定されている取引先責任者レコードページを再読み込みします。

@apiなしのaccountIdは値がセットされておらず、@apiありのaIdには値がセットされていることを確認できました。

すごく簡単な内容ですが、実際に自分で試すことで「こうしたらどうなる?」と新たな疑問もわいてきたりするので、是非自分で実装して確認してみてください。

@trackデコレータ

@trackデコレータは、オブジェクトのプロパティを監視するようにフレームワークに指示するときに使います。

Salesforce Developre Guide: フィールド、オブジェクト、配列のリアクティビティ

@trackデコレータに関しては、この TechBlog で詳しく解説されており、サンプルも掲載されていますので、今回はこの記事の「注意すべきポイントは」の内容だけ確認してみます。

今回のサンプルではContactの情報を全く使いませんが、手っ取り早くいまあるコンポーネントで試してみました。

まず、contact.jsに@trackありのオブジェクトと@tarckなしのオブジェクトを追加します。

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class Contact extends LightningElement {


        //(中略)//

        @track withTrackObject  = {
                label: '@trackあり',
                count: 0
        };
        withoutTrackObject  = {
                label: '@trackなし',
                count: 0
        };
}
contact.js

次に、これらのオブジェクトを表示するようcontact.htmlを編集します。 合わせて、テスト用のボタンを配置します。上記記事によると、@trackを付けていないオブジェクトでも、同時に@trackがついているオブジェクトと一緒に変更されると変更内容が画面に反映さる、とあるので、(1) @trackありだけ変更、(2)@trackなしだけ変更、(3)@trackあり/@trackなしの両方を変更する3つのボタンを用意しました。

<template>

        (中略)

        <lightning-card title="track test">
                <div>
                        {withTrackObject.label}: {withTrackObject.count}
                </div>
                <div>
                        {withoutTrackObject.label}: {withoutTrackObject.count}
                </div>

                <lightning-button
                        label="@tarckありだけ変更"
                        onclick={handleChangeWithOnlyClick}>                
                </lightning-button>
                <lightning-button
                        label="@tarckなしだけ変更"
                        onclick={handleChangeWithoutOnlyClick}>                
                </lightning-button>
                <lightning-button
                        label="両方変更"
                        onclick={handleChangeBothClick}>                
                </lightning-button>
        </lightning-card>
</template>
contact.html

contact.jsに戻って、追加したボタンクリック時の処理を追加します。

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';

export default class Contact extends LightningElement {


        //(中略)//

      @track withTrackObject  = {
                label: '@trackあり',
                count: 0
        };
        withoutTrackObject  = {
                label: '@trackなし',
                count: 0
        };

         // ↓ここに追加
        handleChangeWithOnlyClick(event) {
                console.log('before', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject) );
                this.withTrackObject.count++;
                console.log('after', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject));
        }

        handleChangeWithoutOnlyClick(event) {
                console.log('before', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject) );
                this.withoutTrackObject.count++;
                console.log('after', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject));
        }

        handleChangeBothClick(event) {
                console.log('before', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject) );
                this.withTrackObject.count++;
                this.withoutTrackObject.count++;
                console.log('after', JSON.stringify(this.withTrackObject), JSON.stringify(this.withoutTrackObject));
        }

}
contact.js

デプロイし、任意の取引先責任者レコードページを開き、各ボタンをクリックして確認します。

以下、自分で動かして確認してみてください。

(1)「@trackありだけ変更」をクリックすると、@trackありの値のみ更新されます。

(2)「@trackなしだけ変更」をクリックすると、コンソール出力で@trackなしの値が変更されてることを確認できますが、この変更は画面には反映されません。意図した通りの@trackなしの動きです。

(3)「両方変更」をクリックすると、@trackなしのほうも、画面に変更が反映されます。これが「注意すべきポイントは」に書かれていた動きです。

最後に

今回はざっくり動きを確認することを目的に簡単なサンプルを作成しました。 この中では試していないものも多々ありますので、もっと掘り下げたい方は、 デコレータ ページからのリンクしているページなどを見て、気になるところを実装して動きを確認してみてください。

個人的には ここ ここ に書かれている@track の制限「項目にオブジェクトまたは配列が含まれる場合、追跡される変更の深度には制限があります。」が気になります。どれくらいの深さまで追跡されるのか、実験してみるのも楽しいかと思います。

最後までお読みいただき、ありがとうございました。

66 件
     
  • banner
  • banner

関連する記事