2023.02.15

【LWCレシピ】Datatableのパフォーマンス向上

はじめに

最近弊社のTechBlogでもLightning Web Component(以下、LWC)の記事がたくさん出ています。それだけ使用する機会が増えてきたということでしょう。
今回は、その中でも使用することが多いであろうDatatableのパフォーマンスを向上させる方法について紹介します。​

Datatableとは

Datatableは、レコードを表形式で表示できる機能です。
Salesforceには標準でリストビューという便利な機能がありますが、カスタムのページに表示したい場合や、Salesforceの項目以外の値を表示・入力したい場合はDatatableが使用できます。
詳しくは公式リファレンスを参照ください。
公式リファレンス

Datatableのパフォーマンス

上記のように10件程度のレコードを表示するのであればパフォーマンスに問題はありません。しかし100件・200件単位のデータを表示する場合は、描画時間の増大等の問題が生じてきます。
これからDatatableで大量データを表示するための2つの方法を紹介します。
1.ページネーション
2.無限スクロール
どちらも一般的に使用されている技術ですので詳細については割愛しますが、SalesforceのDatatableで実装することで表示パフォーマンスを改善することができます。

ページネーション

ページネーションは、情報を複数ページに分割することで利用者がデータを見やすくする方法です。AmazonやGoogleなど各種サイトで利用されています。
今回は1ページ最大10件表示するDatatableを実装します。

データ自体はApexクラスで一括で取得し、配列のsliceを使用して表示するレコードを制御しています。
処理詳細はコメントに記載しています。
import { LightningElement, api, wire } from 'lwc';
import showOpportunityList from '@salesforce/apex/OpportunityTableLWC.showOpportunityList';

const COLUMNS = [
    { label: '商談名', fieldName: 'Name', type: 'text', sortable: true, cellAttributes: {alignment: 'left'} },
    { label: 'フェーズ', fieldName: 'StageName'},
    { label: '完了予定日', fieldName: 'CloseDate', type: 'date' },
    { label: '金額', fieldName: 'Amount', type: 'currency'},
];

export default class OpportunityTableWithPagination extends LightningElement {
    @api recordId;
    opportunities = [];
    columns = COLUMNS;
    allItems = []
    // ページ番号
    page = 1;
    // 1ページに表示するレコード数
    showSize = 10;
    // ページの最初のレコードのindex
    startRecord = 1;
    // ページの最後のレコードのindex
    endRecord = 0;
    // 合計ページ数
    totalPage = 0;
    totalRecount = 0;

    /**
     * 最初のページか判定
     */
    get isFirstPage() {
        return this.page === 1;
    }

    /**
     * 最後のページか判定
     */
    get isLastPage() {
        return this.page === this.totalPage;
    }

    @wire(showOpportunityList, {accId: '$recordId'})
    wiredOpportunity(result) {
        if(result.data) {
            this.allItems = result.data;
            this.totalRecount = this.allItems.length;
            // 総ページ数を取得
            this.totalPage = Math.ceil(this.totalRecount / this.showSize);
            // 最初のページに表示するレコードを取得
            this.opportunities = this.allItems.slice(0, this.showSize);
            this.endRecord = this.showSize;
        }
    }

    /**
     * 前へボタン押下時の処理
     */
    previousHandler() {
        if(this.page > 1) {
            this.page -= 1;
            this.pageChangeHandler(this.page);
        }
    }

    /**
     * 次へボタン押下時の処理
     */
    nextHandler() {
        if(this.page < this.totalPage) {
            this.page += 1;
            this.pageChangeHandler(this.page);
        }
    }

    /**
     * ページ変更時の処理
     */
    pageChangeHandler(page) {
        // 表示するページの最初のレコードのindex
        this.startRecord = ((page - 1) * this.showSize);
        // 表示するページの最後のレコードのindex
        this.endRecord = (page * this.showSize);
        // 最後のレコードが総レコード数より多い場合はレコードサイズをセット
        if(this.endRecord > this.totalRecount) {
            this.endRecord = this.totalRecount;
        }

        this.opportunities = this.allItems.slice(this.startRecord, this.endRecord);
        this.startRecord += 1;
    }
}
opportunityTableWithPagination.js
<template>
    <lightning-card title="商談一覧" icon-name="custom:custom66">
      <lightning-datatable
        key-field="id"
        data={opportunities}
        columns={columns}
        hide-checkbox-column="true">
      </lightning-datatable>
      <div class="slds-m-around_medium">
        <p class="slds-m-vertical_medium content">
                {totalRecount}件中 {startRecord}件~{endRecord}件を表示中。
                {page}/{totalPage}ページ</p>
        <lightning-layout>
            <lightning-layout-item>
                <lightning-button label="前へ" icon-name="utility:chevronleft" onclick={previousHandler} disabled={isFirstPage}></lightning-button>
            </lightning-layout-item>
          <lightning-layout-item flexibility="grow"></lightning-layout-item>
            <lightning-layout-item>
                <lightning-button label="次へ" icon-name="utility:chevronright" icon-position="right" onclick={nextHandler} disabled={isLastPage}></lightning-button>
            </lightning-layout-item>
      </lightning-layout>
      </div>
    </lightning-card>
</template>
opportunityTableWithPagination.html

無限スクロール

無限スクロールは初期表示として一定件数のレコードを表示し、スクロールするたびに追加でレコードを取得していく方法です。ページネーションとは違い、1つのページにすべてのレコードを表示することができます。リストビューでも使われています。
画像だと分かりづらいですが、下にスクロールするごとに新たに20件追加で表示します。あらかじめ指定した数に達したら、スクロールできないようにします。

Datatableには標準で無限スクロール用の機能があります。lightning-datatableでenable-infinite-loading属性を追加し、onloadmoreでスクロール時に実行する関数を指定します。

<template>
    <lightning-card title="商談一覧" icon-name="custom:custom66">
      <div class="slds-box" style="height:400px;">
        <template if:true={opportunities}>
          <lightning-datatable
            key-field="Id"
            data={opportunities}
            columns={columns}
            hide-checkbox-column
            enable-infinite-loading
            onloadmore={loadNextData}>
          </lightning-datatable>
        </template>
      </div>
      ステータス:{status}
    </lightning-card>
</template>
opportunityTableWithScroll.html
import { LightningElement, api } from 'lwc';
import getOpportunityWithOffSet from '@salesforce/apex/OpportunityTableLWC.getOpportunityWithOffSet';

const COLUMNS = [
    { label: '商談名', fieldName: 'Name', type: 'text'},
    { label: 'フェーズ', fieldName: 'StageName'},
    { label: '完了予定日', fieldName: 'CloseDate', type: 'date' },
    { label: '金額', fieldName: 'Amount', type: 'currency'},
];

export default class OpportunityTableWithScroll extends LightningElement {
    @api recordId;
    columns = COLUMNS;
    opportunities = []
    limitRow = 20;
    rowOffSet = 0;
    status;
    limitSize = 209;
    targetDataTable;
    isFinish = false;

    connectedCallback() {
        this.loadData();
    }

    loadData() {
        getOpportunityWithOffSet({accId: this.recordId, limitSize: this.limitRow, offSet: this.rowOffSet})
        .then(result => {
            let tempRecordList = [];
            for(let i = 0; i < result.length; i++) {
                let tempRecord = Object.assign({}, result[i]);
                tempRecordList.push(tempRecord);
            }
            // 取得したレコードを既存レコードリストに追加
            let currentShowRecords = [...this.opportunities, ...tempRecordList];
            this.opportunities = currentShowRecords;
            this.status = '';
            // これ以上取得するレコードがない場合はスクロール出来ないようにする
            if(this.opportunities.length >= this.limitSize && tempRecordList.length === 0) {
                this.targetDataTable.enableInfiniteLoading = false;
                this.status = 'これ以上データはありません。';
                this.isFinish = true;
            }
            if(this.targetDataTable) {
                this.targetDataTable.isLoading = false;
            }
        }).catch(error => {
        })
    }

    /**
     * スクロール時の処理
     */
    loadNextData(event) {
        event.preventDefault();
        if(!this.isFinish) {
            event.target.isLoading = true;
            this.targetDataTable = event.target;
            this.status = 'ローディング中';
    
            this.rowOffSet += this.limitRow;
            console.log(this.rowOffSet);
            this.loadData();
        }
        
    }
}
opportunityTableWithScroll.js
Apexのコントローラー側ではSOQLでOFFSETを使用してレコードを取得します。
public with sharing class OpportunityTableLWC {

    @AuraEnabled
    public static List<Opportunity> getOpportunityWithOffSet(Id accId, Integer limitSize, Integer offSet){
        return [select Id, Name, StageName, CloseDate, Amount, Account.Name
                from Opportunity
                where AccountId =: accId
                LIMIT :limitSize
                OFFSET :offSet
                ];
    }
}
OpportunityTableLWC.cls

おわりに

レコードを表示する方法はたくさんありますが、標準のDatatableでできることが増えれば実装も楽になると思います。
カスタムでレコード一覧を表示する機会がある場合は、試してみてください。
23 件
     
  • banner
  • banner

関連する記事