Lightning Web Componentにおけるデータ参照/更新/登録

## 参照
Lightning web componentにおけるデータ取得/更新/登録のやり方を記載する。

## 内容
### getRecordを使ったデータ取得

````html:html
<template>
    <lightning-card>
        <div class="slds-m-around_medium">
            <!-- 1.0 -->
            <template if:true={account.data}>
                <div>
                    <p>Name:{name}</p>
                </div>
                <div>
                    <p>Phone:{phone}</p>
                </div>
                <div>
                    <p>Annual Revenue:{account.data.fields.AnnualRevenue.value}</p>
                </div>
            </template>
        </div>
    </lightning-card>
</template>
````
※1.0：if:true={account.data}は必須。これがないとエラーになる。画面が描画された時はaccount.dataに値が入っていないため。

````javascript:javascript
import { LightningElement, api,wire } from 'lwc';
import { getRecord,getFieldValue } from 'lightning/uiRecordApi';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import ACCOUNT_PHONE from '@salesforce/schema/Account.Phone';

export default class Sample1 extends LightningElement {

    @api recordId;

    // 1.0
    @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME, ACCOUNT_PHONE,'Account.AnnualRevenue'] })
    account;

    get name() {
        // 2.0
        return getFieldValue(this.account.data, ACCOUNT_NAME);
    }

    get phone() {
        // 3.0
        return this.account.data.fields.Phone.value;
    }

}
````
※1.0：getRecordを使うとレコードを取得できる。第一引数にレコードのID/第二引数に取得するフィールドを指定。getRecordをimportするのを忘れずに。
※2.0：getFieldValueを使うと、wireサービスで取得した値から欲しいフィールドを取得できる
※3.0：getFieldValueを使わない場合の値の取り方。valueを忘れずに。特別な理由がないかぎり、getFieldValueを使う。

### getRecordを使ったデータ取得（例外処理付き）

````html:html
<template>
    <lightning-card>
        <div class="slds-m-around_medium">
            <template if:true={account}>
                <div>
                    <p>Name:{name}</p>
                </div>
                <div>
                    <p>Phone:{phone}</p>
                </div>
                <div>
                    <p>Annual Revenue:{account.fields.AnnualRevenue.value}</p>
                </div>
            </template>
        </div>
    </lightning-card>
</template>
````

````javascript:javascript
import { LightningElement, api,wire,track } from 'lwc';
import { getRecord,getFieldValue } from 'lightning/uiRecordApi';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import ACCOUNT_PHONE from '@salesforce/schema/Account.Phone';

export default class Sample2 extends LightningElement {

    @api recordId;
    // 2.0
    @track account;

    // 1.0
    @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME, ACCOUNT_PHONE,'Account.AnnualRevenue'] })
    wiredAccount({ data, error }) {
        if (data) {
            this.account = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.account = undefined;
        }
    }

    get name() {
        return getFieldValue(this.account, ACCOUNT_NAME);
    }

    get phone() {
        return this.account.fields.Phone.value;
    }

}
````
※1.0：wireサービスの結果は、変数に渡すこともできる。
※2.0：構造体（オブジェクト）には、@trackを付ける。これを付けないと、構造体はリアクティブにならない。

### updateRecordを使ったデータ更新

````html:html
<template>
    <lightning-card>
        <div class="slds-m-around_medium">
            <template if:true={account.data}>
                <lightning-input label="Name" value={account.data.fields.Name.value} data-field="Name" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-input label="Phone" value={account.data.fields.Phone.value} data-field="Phone" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-button label="Update Account" variant="brand" onclick={updateAccount}></lightning-button>
            </template>
        </div>
    </lightning-card>
</template>
````

````javascript:javascript
import { LightningElement, track, wire, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord,updateRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import Phone_FIELD from '@salesforce/schema/Account.Phone';
import ID_FIELD from '@salesforce/schema/Account.Id';

export default class Sample3 extends LightningElement {
    
    @api recordId;
    @track error;

    @wire(getRecord,{recordId: '$recordId',fields: [NAME_FIELD,Phone_FIELD]})
    account;

    // 1.0
    updateAccount() {

        // バリデーションチェック
        const allValid = [...this.template.querySelectorAll('lightning-input')]
            .reduce((validSoFar, inputFields) => {
                inputFields.reportValidity();
                return validSoFar && inputFields.checkValidity();
            }, true);

        if (allValid) {

            const fields = {};
            fields[ID_FIELD.fieldApiName] = this.recordId;
            fields[NAME_FIELD.fieldApiName] = this.template.querySelector("[data-field='Name']").value;
            fields[Phone_FIELD.fieldApiName] = this.template.querySelector("[data-field='Phone']").value;
            const recordInput = { fields };

            // 2.0
            updateRecord(recordInput)
                .then(() => {
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Success',
                            message: 'Contact updated',
                            variant: 'success'
                        })
                    );
                })
                .catch(error => {
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Error updating record',
                            message: error.body.message,
                            variant: 'error'
                        })
                    );
                });
            }
        else {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Something is wrong',
                    message: 'Check your input and try again.',
                    variant: 'error'
                })
             );
        }
    }
}
````
※1.0：html側のボタンを押したときに動くメソッド。html側のボタンのonclick属性で指定されている。
※2.0：updateRecordを使ってレコードを更新できる。importするのを忘れずに。

### createRecordを使ったデータ登録

````html:html
<template>
    <lightning-card>
        <div class="slds-m-around_medium">
            <template if:true={account.data}>
                <lightning-input label="Name" value={account.data.fields.Name.value} data-field="Name" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-input label="Phone" value={account.data.fields.Phone.value} data-field="Phone" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-button label="Create Account" variant="brand" onclick={createAccount}></lightning-button>
            </template>
        </div>
    </lightning-card>
</template>
````

````javascript:javascript
import { LightningElement, track, wire, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord,createRecord } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import Phone_FIELD from '@salesforce/schema/Account.Phone';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';

export default class Sample4 extends LightningElement {
    
    @api recordId;
    @track error;

    @wire(getRecord,{recordId: '$recordId',fields: [NAME_FIELD,Phone_FIELD]})
    account;

    createAccount() {

        const allValid = [...this.template.querySelectorAll('lightning-input')]
            .reduce((validSoFar, inputFields) => {
                inputFields.reportValidity();
                return validSoFar && inputFields.checkValidity();
            }, true);

        if (allValid) {

            const fields = {};
            fields[NAME_FIELD.fieldApiName] = this.template.querySelector("[data-field='Name']").value;
            fields[Phone_FIELD.fieldApiName] = this.template.querySelector("[data-field='Phone']").value;
            const recordInput = { apiName: ACCOUNT_OBJECT.objectApiName,fields };

            // 1.0
            createRecord(recordInput)
                .then(() => {
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Success',
                            message: 'Contact updated',
                            variant: 'success'
                        })
                    );
                    // return refreshApex(this.account);
                })
                .catch(error => {
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Error creating record',
                            message: error.body.message,
                            variant: 'error'
                        })
                    );
                });
            }
        else {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Something is wrong',
                    message: 'Check your input and try again.',
                    variant: 'error'
                })
             );
        }
    }
}
````
※1.0：craeteRecordでレコードを作成する。importを忘れずに。基本的にupdateRecordと同じだが、apiNameの指定が必要な点が異なる。

### Apexメソッドを呼び出してデータ取得/更新

````java:apex
public with sharing class AccountController {

    // 1.0
    @AuraEnabled(cacheable=true)
    public static Account getSingleAccount(String id) {
        return [
            SELECT Id, Name, Phone
            FROM Account
            WHERE Id = :id
            WITH SECURITY_ENFORCED
        ];
    }

    // 2.0
    @AuraEnabled(cacheable=false)
    public static Account updateSingleAccount(Account account){
        update account;
        return account;
    }
}

````
※1.0：AuraEnabledを付けるとLWCから呼び出せるようになる。データ取得系のメソッドにはcacheable=trueを付ける（推奨）
※2.0：AuraEnabledを付けるとLWCから呼び出せるようになる。データ更新系のメソッドにはcacheable=falseを付ける（必須）

````html:html
<template>
    <lightning-card>
        <div class="slds-m-around_medium">
            <template if:true={account.data}>
                <lightning-input label="Name" value={account.data.Name} data-field="Name" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-input label="Phone" value={phone} data-field="Phone" class="slds-m-bottom_x-small"></lightning-input>
                <lightning-button label="Update Account With Calling Apex" variant="brand" onclick={updateAccount}></lightning-utton>
            </template>
        </div>
    </lightning-card>
</template>
````

````javascript:javascript
import { LightningElement, track, wire, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';
import getSingleAccount from '@salesforce/apex/AccountController.getSingleAccount';
import updateSingleAccount from '@salesforce/apex/AccountController.updateSingleAccount';

export default class Sample5 extends LightningElement {
    
    @api recordId;
    @track error;

    // 1.0
    @wire(getSingleAccount, { id: '$recordId'})
    account;

    // 2.0
    get phone() {
        return this.account.data.Phone;
    }

    updateAccount() {

        const allValid = [...this.template.querySelectorAll('lightning-input')]
            .reduce((validSoFar, inputFields) => {
                inputFields.reportValidity();
                return validSoFar && inputFields.checkValidity();
            }, true);

        if (allValid) {

            let new_account = {};
            new_account.Id = this.recordId;
            new_account.Name = this.template.querySelector("[data-field='Name']").value;
            new_account.Phone = this.template.querySelector("[data-field='Phone']").value;

            // 3.0
            updateSingleAccount({ account: new_account })
                .then((result) => {
                    console.log(result);
                    // 4.0
                    refreshApex(this.account)
                        .then(() => {
                            this.dispatchEvent(
                                new ShowToastEvent({
                                    title: 'Success',
                                    message: 'Account created',
                                    variant: 'success'
                                })
                            );
                        });
                })
                .catch((error2) => {
                    console.log(error2);
                    this.dispatchEvent(
                        new ShowToastEvent({
                            title: 'Error creating record',
                            message: 'Error',
                            variant: 'error'
                        })
                    );
                });

        }else {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Something is wrong',
                    message: 'Check your input and try again.',
                    variant: 'error'
                })
             );
        }
    }
}
````
※1.0：wireサービスでApexのメソッドを指定する。importを忘れずに。
※2.0：Apexのメソッドの戻り値の取得のやり方。fieldsとかvalueとかいらない点に注意。
※3.0：Apexのメソッドを呼び出し。importを忘れずに
※4.0：データを更新した場合、最新の情報を再取得する必要あり。Apexメソッド（ここではgetSingleAccount）を使っている場合は、refreshApexを呼び出して再取得する必要あり。getRecordを使っている場合は勝手に取得されるので不要。

## 補足
### wireサービスの引数に動的な変数を渡すときは$を付ける
変数に$を付けてwireサービスの引数に指定すると、変数の値が変わった場合にwireサービスが最新の値を取得しなおしてくれる。
基本的には、下記の考えで$を付けたらいい。
- 引数が動的な値の場合$を付ける
- 引数が固定値（フィールド値とか）の場合$はつけない

### Apexメソッドでデータを取得していて再取得したいときはrefreshApexを使う
Apexメソッドを呼び出してデータを取得しているLWCでデータの更新をした場合、refreshApexを呼び出して最新の値を取り直すこと。

### やり方がいろいろあるのはわかったけど、どれを使えばいいのか
以下の順番に検討する
- 基本的にはlightning-record-*-formを使う（参照）
- lightning-record-*-formで実現できない複雑なフォームを使う場合はLightningデータサービスワイヤアダプタ（getRecordとか）を使う
- 上記で対応できない特殊なケース(※)でApexを使う

※Apexを使う特殊なケース
- getRecordとかで取得できない特殊なオブジェクト (ToDo や行動など) を取り扱う場合
- getRecordで取り扱えない大きなデータ（200件以上のレコードとか）を取り扱う場合
- 複数のオブジェクトを更新してトランザクション（ロールバック）を使いたい場合

### getRecordとかupdateRecord以外にもJavaScript APIってあるの？
ここにある。
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_lightning_ui_api_record

## 参考
https://developer.salesforce.com/docs/component-library/documentation/ja-jp/lwc/lwc.data_guidelines
https://developer.salesforce.com/docs/component-library/documentation/ja-jp/lwc/lwc.data_wire_service_about