反省をざっくりまとめると?
- LWC Dev Guideに紹介されているLWCレシピ集(railheadapps/lwc-recipes - Github
)以上のことはしない - レシピ集以上のことをしたいならば、レシピを組み合わせて実現する
- レシピを組み合わせてもできないことはそもそも設計が間違っている可能性大
私のスキル
LWCの開発をすることになったフロントエンド1年生がやらかした失敗と反省の話を書きます。
スキルレベルを見るとわかるようにサーバサイドの開発ばかりで、フロントエンド開発からは距離があったので、モダンな開発がよくわかってなかったので非効率で不適切な構造にしてしまいました。間違っていることを書いている場合はコメントください。
今後はこんなことがないように自戒として残します。
スキル
- Ruby: 1年以上
- PHP: 2年以上
- Apex: 2年以上
- JavaScript: 3年以上
- es6, JSフレームはほぼ未経験
何をやらかした?
Salesforceをやっていると外部システムとAPI通信し連携することがよくあります。例えば、外部システムの主キーをSalesforceの項目に保存し、何らかの処理をトリガーにして主キーを元にAPIをコールする。あるあるですね。
Classicではスケジューラやトリガー、Visualforce(Ajax)から通信していたと思いますが、LightningではLWC(Lightning Web Component)を標準の画面に追加し、LWC内のボタンイベントなどをトリガーに通信することができます。
今回はApexクラスからAPIをコールすることを想定しています。
ボタンイベントだと問題はなかったのですが、ページが読み込まれたときに、Salesforceに保存されている外部システムの値を利用してAPIを叩くときに変なことをしてしまいました。
まずはやらかしたコードから見ていきます。コードの全文を乗せると見づらいので一部を記述します。完全なコードはGistにまとめました。
Afterコード完全版: 修正コード
コンポーネント概要
・ 詳細ページ表示時に保存されている値をAPIに渡し、返り値をLWCで表示する
・ 外部システムはLivedoor天気API(2020年7月提供終了)とする
・ Salesforceには地域別に定義されたID番号(項目名:location_number)が保存されている(例:佐賀県 伊万里 => 410020)
やらかしコード
詳細レコード取得時にワイヤード関数を利用してAPIをコールしています。
なにが失敗かというと、このコンポーネントのテストを書くことをイメージすると分かりやすいです。
詳細レコード取得テストのために関係のないAPIコールApexのモックが必要になります。
つまり、不必要に複雑な手順を踏まなければテストができません。
これでは改修、テストが難しく将来的に不必要なバグを抱える可能性があります。
<template if:true={record}>
<div class="title">
<!-- 詳細ページの情報を表示 -->
<p>所在地名:{record.location}</p>
<p>所在地番号:{record.location_number}</p>
</div>
<hr>
<div class="description">
<!-- 結果を子コンポーネントで表示 -->
<p>天気概況</p>
<p>{description}</p>
</div>
</template>
// Livedoor天気APIをコールするApexクラス
import todayWeather from '@salesforce/apex/WeatherAPI.today';
/**
* 詳細ページの所在地名、所在地番号を取得
*/
export default class Before extends LightningElement {
/** バインド変数 */
record;
description;
error;
/** 詳細データ取得 */
@api recordId;
@wire(getRecord, { recordId: '$recordId', ['Obj__c.Location__c', 'Obj__c.LocationNumber__c'] })
wiredFunction({ error, data }) {
if (data) {
this.record = data;
this.error = undefined;
// 保存されているデータを利用してAPIコール
todayWeather({
city: this.record.LocationNumber__c
}).then(result => {
const json = JSON.parse(result);
this.description = json.description.text;
}).catch(err => {
this.error = err;
});
} else if (error) {
this.record = undefined;
this.error = error;
}
}
}
反省コード
修正点
- 詳細ページの情報は親コンポーネントのgetterから取得
- APIコールの結果表示を子コンポーネントに移動
- APIコールApexは子コンポーネントのsetterで呼び出し
2.3.の修正で詳細ページのテストとAPIコールApex呼び出しのテストを分離し、テストしやすいシンプルな構造に修正できました。
1.はワイヤード関数を無くしたので代替手段へ変更した影響です。
<template if:true={record}>
<div class="title">
<!-- 詳細ページの情報を表示 -->
<p>所在地名:{location}</p>
<p>所在地番号:{location_number}</p>
</div>
<hr>
<div class="description">
<!-- 結果を子コンポーネントで表示 -->
<c-after2 number={location_number}></c-weather-conditions>
</div>
</template>
/**
* 詳細ページの所在地名、所在地番号を取得
*/
export default class After1 extends LightningElement {
/** 詳細データ取得 */
@api recordId;
@wire(getRecord, { recordId: '$recordId', ['Obj__c.Location__c', 'Obj__c.LocationNumber__c'] })
record;
/** getter */
/**
* 詳細データから取得地点名を取得
* @returns String
*/
get location() {
return this.record.data.fields.Location__c.value;
}
/**
* 詳細データから地点番号を取得
* @returns String
*/
get location_number() {
return this.record.data.fields.LocationNumber__c.value;
}
}
<template>
<p>天気概況</p>
<p>{description}</p>
</template>
// Livedoor天気APIをコールするApexクラス
import todayWeather from '@salesforce/apex/WeatherAPI.today';
/**
* 本日の天気概況をLivedoor天気APIから取得
*/
export default class After2 extends LightningElement {
/** バインド変数 */
description;
/** getter */
@api
get number() {
return this.description;
}
/** setter */
set number(value) {
todayWeather({
city: value
})
.then(result => {
const json = JSON.parse(result);
this.description = json.description.text;
})
.catch(err => {
this.error = err;
});
}
}
まとめ
外部システムとの連携で間違ったコードの修正をしました。
記述量、ファイル数が増えるデメリットがありますが、より複雑な処理を実装することを考えるとこれぐらいシンプルなほうがいい気がします。
やらかしコードを本番環境リリース後にチームを離れてしまったので、後始末お願いします
参考
Lightning Web component Dev Guide
trailheadapps/lwc-recipes - Github
LWCのリアクティブプロパティと再レンダリングの関係は複雑だった(第2回) - TerraSkyBase