2020.05.19

LWCのリアクティブプロパティと再レンダリングの関係は複雑だった(第3回)

はじめに

みなさん、こんにちは。
今回は、テラスカイ亀山田伏が実際の開発で直面した事例をもとに調査した「LWCのリアクティブプロパティと再レンダリングの複雑な関係」の第3回となります。

本題に入る前に

もし、LWCについての前提知識に不安がある方は、基本的なことを記載した過去の記事もありますので、参考にしてください。
Lightning Web ComponentsとHappyになれる方法(基礎編)

複雑な関係をまとめてみた、これで使いこなせるのか?

最終回となる今回は、第1回、第2回で紹介した内容も合わせて、リアクティブプロパティと再レンダリングの関係について整理したいと思います。

リアクティブプロパティとは

①プロパティは全てリアクティブ(@track デコレータは不要)
※ Spring'20 での仕様変更

②公開プロパティ(@api デコレータ)はリアクティブプロパティ

リアクティブプロパティ値の変更による再レンダリングについて

HTML内で使用されているか、HTML内で使用しているプロパティの getter で参照されている場合に、再レンダリングされる
(第2回で事例紹介しています)

② オブジェクトのプロパティや配列の要素などの変更を追跡して再レンダリングしたい場合は @track デコレータが必要
※ 公的プロパティ(@api デコレータ)も、オブジェクトのプロパティや配列の要素などの変更を追跡します

③ 前回、設定した値と同じ値を設定した場合は、再レンダリングされない
(第1回で事例紹介しています)

再レンダリング時はテンプレートで使用されるすべての式を再評価

① 再レンダリングされると、リアクティブプロパティ以外も再評価して表示される

リアクティブプロパティと再レンダリングの関係において、知っておいた方が良いと思われる内容は、以上となります。

最後に、上記の「再レンダリングされると、リアクティブプロパティ以外も再評価して表示される」点について、少しだけ深堀りしたいと思います。

注意すべきポイントは

再レンダリング時に、すべての式が再評価されて表示されること自体は特に問題ではないのですが、本来であれば値の変更が行われても画面に反映されない(リアクティブでない)プロパティに対しても、再評価されるという点について認識しておくことが重要です。
例えば、あるオブジェクトAに対して @track の記述が漏れてしまった場合を考えてみましょう。

動作確認テストにて、オブジェクトAのプロパティを変更するような操作をした結果、変更内容が画面に反映されなければ、@track の記述が漏れていることに気付きますよね。

しかし、その操作が、オブジェクトAのプロパティだけでなく、他のリアクティブプロパティの値も変更する操作であった場合はどうでしょうか。

他のリアクティブプロパティの変更により再レンダリングされ、@track が漏れているオブジェクトAについても再評価されて表示されるため、変更内容が画面に反映されます。

つまり、動作確認する際のテストケースとして、オブジェクトAのプロパティだけが変更されるようなケースを用意しておかないと、テストで @track の記述漏れを発見することができないということになります。
以前は、プロパティに対する @track の記述漏れに対しても同様のことが言えたのですが、Spring'20 の仕様変更により、プロパティに対しては @track の記述が不要となったため、対象はオブジェクトのプロパティや配列要素などに限定されました。
とはいえ、注意すべき点かとは思います。

この動作を確認できるサンプル画面を用意しました

以下のコードを Lightning Web Components Playground の app.html 、 app.js に貼り付けるだけで上記の動作を確認できますので、ぜひ試してみてください。
Lightning Web Components Playgroundはこちら
PlaygroundのDeveloperGuidはこちら
<template>
    <div class="app slds-p-around_x-large">
        <div class="slds-p-around_small">
            <table class="slds-table slds-table_bordered slds-table_col-bordered">
                <th >No.</th>
                <th>デコレータ</th>
                <th >Prop</th>
                <th >バインド</th>
                <th></th>
                <th>単一変更</th>
                <th colspan="2">複数変更</th>
                <tr>
                    <td>1</td>
                    <td>@track</td>
                    <td>obj1 = { prop1_1: 0 };</td>
                    <td>{obj1.prop1_1}</td>
                    <td>{obj1.prop1_1}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addObj1}></lightning-button>
                    </td>
                    <td rowspan="2">
                        <lightning-button label="add" variant="brand" onclick={add1and2}></lightning-button>
                    </td>
                    <td>-</td>
                </tr>
                <tr>
                    <td>2</td>
                    <td>なし</td>
                    <td>obj2 = { prop2_1: 0 };</td>
                    <td>{obj2.prop2_1}</td>
                    <td>{obj2.prop2_1}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addObj2}></lightning-button>
                    </td>
                    <td rowspan="2">
                        <lightning-button label="add" variant="brand" onclick={add2and3}></lightning-button>
                    </td>
                </tr>
                <tr>
                    <td>3</td>
                    <td>なし</td>
                    <td>prop3 = 0;</td>
                    <td>{prop3}</td>
                    <td>{prop3}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addProp3}></lightning-button>
                    </td>
                    <td>-</td>
                </tr>
                <tr>
                    <td>4</td>
                    <td>
                        @track<br/>
                        htmlProp
                    </td>
                    <td>htmlProp.obj4 = { prop4_1: 0 };</td>
                    <td>{htmlProp.obj4.prop4_1}</td>
                    <td>{htmlProp.obj4.prop4_1}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addObj4}></lightning-button>
                    </td>
                    <td rowspan="2">
                        <lightning-button label="add" variant="brand" onclick={add4and5}></lightning-button>
                    </td>
                    <td>-</td>
                </tr>
                <tr>
                    <td>5</td>
                    <td>
                        @track<br/>
                        htmlProp
                    </td>
                    <td>htmlProp.obj5 = { prop5_1: 0 };</td>
                    <td>{htmlProp.obj5.prop5_1}</td>
                    <td>{htmlProp.obj5.prop5_1}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addObj5}></lightning-button>
                    </td>
                    <td rowspan="2">
                        <lightning-button label="add" variant="brand" onclick={add5and6}></lightning-button>
                    </td>
                </tr>
                <tr>
                    <td>6</td>
                    <td>なし</td>
                    <td>prop6 = 0;</td>
                    <td>{prop6}</td>
                    <td>{prop6}</td>
                    <td>
                        <lightning-button label="add" variant="brand" onclick={addProp6}></lightning-button>
                    </td>
                    <td>-</td>
                </tr>
            </table>
        </div>
    </div>
</template>
app.html
import { LightningElement, track } from 'lwc';

export default class App extends LightningElement {
    @track
    obj1 = { prop1_1 : 0 }
    obj2 = { prop2_1 : 0 }
    prop3 = 0;

    @track
    htmlProp = {
        obj4 : { prop4_1 : 0 },
        obj5 : { prop5_1 : 0 }
    }
    prop6 = 0;


    addObj1() {
        this.obj1.prop1_1++;
    }
    addObj2() {
        this.obj2.prop2_1++;
    }
    addProp3() {
        this.prop3++;
    }
    addObj4() {
        this.htmlProp.obj4.prop4_1++;
    }
    addObj5() {
        this.htmlProp.obj5.prop5_1++;
    }
    addProp6() {
        this.prop6++;
    }
    add1and2() {
        this.addObj1();
        this.addObj2();
    }
    add2and3() {
        this.addObj2();
        this.addProp3();
    }
    add4and5() {
        this.addObj4();
        this.addObj5();
    }
    add5and6() {
        this.addObj5();
        this.addProp6();
    }
}
app.js
以下のような画面が表示されましたか?

この画面では、
「単一変更」列に表示されている「add」ボタンは、その行のプロパティ値に、1 を加算します。
「複数変更」列に表示されている「add」ボタンは、2行を対象としており、それぞれの行のプロパティ値に、1 を加算します。

No.2 行が、@track の記述漏れしたオブジェクトだと考えてください。

No.1、2 行や、No.2、3 行の「複数変更」列の「add」ボタンのような、正しく再レンダリングされるプロパティ値の変更も同時に実施されるようなケースで動作確認した場合には、No.2 行の「値」列に表示された値に加算されたプロパティ値が表示され、動作だけでは、@track の記述漏れに気付けません。

No.2 行の「単一変更」列に表示されている「add」ボタンのように、そのオブジェクト単体を変更するケースで動作確認することで、No.2 行の「値」列に表示された値が変更されないことが確認でき、@track の記述漏れに気付くことができます。

サンプル画面の補足

Spring'20 によりプロパティについては @track が不要になったため、大量に @track を記述するような実装をする機会はあまりないかも知れませんが、上記のような @track の記述漏れを複数のオブジェクトに対して考慮しないで済む実装方法をひとつご紹介します。

先ほどのサンプル画面のNo.4、5 では、HTMLにバインドするオブジェクトをひとまとめにする「htmlProp」というオブジェクトを用意し、このオブジェクトに @track を記述しています。
これにより、オブジェクトのプロパティの変更に対しても追跡してくれるようになるため、「obj4」と「obj5」のそれぞれに @track を記述する必要がありません。

サンプル画面においては、No.4、5 以外にもプロパティを定義して @track を記述していますが、HTMLにバインドする項目やオブジェクトなどは全て、この「htmlProp」オブジェクトのプロパティとして定義する​といったルールで実装すれば、@track の記述を1つのみに限定することができます。
このような実装であれば、動作確認する際にあまりケースを気にせず、何かしらの値の変更による再レンダリングを確認すれば良いので、@track の記述漏れについての考慮が必要なくなります。

この実装方法では、プロパティの階層が深くなります。弊害なども考慮した上で参考にしていただければと思います。

おわりに

全3回にわたり、リアクティブプロパティと再レンダリングに関連する内容を紹介させていただきましたが、いかがでしたか?
この記事が少しでもみなさんのお役に立てれば幸いです。

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

関連する記事