初めての Lightning Web コンポーネント開発で OCI を操作してみる（気づき・困った編）

※ これから記載する事項は、私が所属する会社とは一切関係のない事柄です。

これまでの
* [初めての Lightning Web コンポーネント開発で OCI を操作してみる（開発方法編）](https://qiita.com/syunyama/items/8c591c9fbe89731204a0)
* [初めての Lightning Web コンポーネント開発で OCI を操作してみる（開発内容編）](https://qiita.com/syunyama/items/863be1541d77957936ec)
 
に続いて、今回は初めて lwc を開発してみて気づいたことや困ったことについて列挙していきたいと思います。

## wire アノテーションをつけたメソッドは 2 回実行される

[ドロップダウンの選択肢を取得する部分](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/lwc/ociInvRegLocationGroup/ociInvRegLocationGroup.js#L78)で、初め wire はデータ取得した時か、エラーが発生した時かのいずれか一回だけメソッドが呼ばれると思っていたので、下記のようなにローディング中の状態の変更に当たる `this.ready = true;` をメソッドの一番下に書いていました。
しかし、なぜかデータがないのにローディングが終わってしまっている見た目となったので、調べてみたら[Stack Exchange](https://salesforce.stackexchange.com/questions/374444/lwc-wire-service-firing-twice)にもあるように wire をつけたメソッドは2回呼び出されるようです。
ちなみに1回目の呼び出しでは、 data にも error にも undifined が入っていました。

```js
@wire(getLocationGroups, { limitNum: 30 })
wiredLocationGroups({ error, data }) {
    if (data) {
        if (data.length > 0) {
            this.options = data.map((d) => { return { label: d.LocationGroupName, value: d.ExternalReference } });
            this.locationGroupId = this.options[0].value;
        }
    } else if (error) {
        console.error(error)
        showToast(this, OCI_INV_REG_RetrievingLocationGroupFail, error.statusText, "error")
    }
    this.ready = true;
}
```

そのため、今回は下記のようにデータがあったときにローディング終了になるように data の有無をチェックした後に状態を変えるように変更しました。

```js
@wire(getLocationGroups, { limitNum: 30 })
wiredLocationGroups({ error, data }) {
    if (data) {
        if (data.length > 0) {
            this.options = data.map((d) => { return { label: d.LocationGroupName, value: d.ExternalReference } });
            this.locationGroupId = this.options[0].value;
        }
        this.ready = true;
    } else if (error) {
        console.error(error)
        showToast(this, OCI_INV_REG_RetrievingLocationGroupFail, error.statusText, "error")
    }
}
```



## Apex コントローラの引数には Javascript のオブジェクトを渡す


例えば、下記のように Javascript の string を渡す際は

```js
getTest({val:'テスト'})
```

Apex 側は下記のように対応する型を引数に指定してあげればいいのですが、

```java
@AuraEnabled(cacheable=true)
public static String getTest(String val) {
    return val + 'Test' 
}
```

下記のように Javascript のオブジェクトを引数として扱いたい場合は

```js
const resuest = { val : 'テスト'}
getTest({resuest})
```

下記のように AuraEnabled と　{ get; set; } を利用して行うようです。

```java
public class TestClass {
    @AuraEnabled
    public String val { get; set; }
}

@AuraEnabled(cacheable=true)
public static String getTest(TestClass resuest) {
    return resuest.val + 'Test' 
}
```

詳しくはヘルプの [Passing Data to an Apex Controller](https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_apex_pass_data.htm) をご覧ください。


## track アノテーションの扱い

track アノテーションは Spring ’20 後で動きが変わったそうです。
以前はリアクティブに反応するプロパティには監視のために全てアノテーションをつけなければならなかったのですが、現在はオブジェクトや配列プロパティ内部の監視をするためのみに利用します。
例えば、下記のように string がプロパティの場合はボタンをクリックすると「クリックされた」という文字列が表示されますが、

```js
export default class Child extends LightningElement {
    val = ''
    onClick() {
        this.val = 'クリックされた'
    }
}
```

```html
<template>
    <div>{val}</div>
    <button onclick={onClick}>ボタン</button>
</template>
```

![image (43).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/6fe8c75b-ea9d-132d-b582-dd2abfd708d0.png)

下記のようにtrackをつけずにオブジェクトの内部を変更したコンポーネントがあった場合、ボタンをクリックしても**なにも表示されません。**

```js
export default class Test extends LightningElement {
    val = { test: '' }
    onClick() {
        this.val.test = "クリックされた"
    }
}
```

```html
<template>
    <div>{val.test}</div>
    <button onclick={onClick}>ボタン</button>
</template>
```

表示させるには下記のように `@track` をつける必要があります。

```js
export default class Test extends LightningElement {
    @track val = { test: '' }
    onClick() {
        this.val.test = "クリックされた"
    }
}
```



## 多言語化・国際化


多言語化はカスタムラベルと翻訳ファイルを利用します

[サンプル](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/labels/CustomLabels.labels-meta.xml)

```xml:force-app/main/default/labels/CustomLabels.labels-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata">
    <labels>
        <fullName>SAMPLE_TEXT</fullName>
        <language>en_US</language>
        <protected>false</protected>
        <shortDescription>Sample Text</shortDescription>
        <value>Sample Text</value>
    </labels>
</CustomLabels>
```

[サンプル](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/translations/ja.translation-meta.xml)

```xml:force-app/main/default/translations/ja.translation-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<Translations xmlns="http://soap.sforce.com/2006/04/metadata">
    <customLabels>
        <label>サンプルテキスト</label>
        <name>SAMPLE_TEXT</name>
    </customLabels>
</Translations>
```

表示する際はコンポーネントで `@salesforce/label/c.~` のフォーマットでインポートして利用します。

```js
import SAMPLE_TEXT from '@salesforce/label/c.SAMPLE_TEXT';

export default class Test extends LightningElement {
   label = { SAMPLE_TEXT }
}
```

```html
<template>
    <div>{label.SAMPLE_TEXT}</div>
</template>
```


その他時間や通貨の表示のローカライズはヘルプの [Internationalization プロパティへのアクセス](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/55.0/lwc/lwc.create_i18n)を参照




## コンポーネントの引数はケバブケースに

これは簡単な話ですが、 React 感覚でやって、2回くらい怒られて覚えたので書いときます。エラー内容は `sampleText="サンプル" is not valid attribute for c-test. All attributes name should be all lowercase.`

下記のように Javascript 内で `sampleText`のようにキャメルケースのプロパティを公開していたら `sample-text` というふうにケバブケースに変換して引数に値を渡します。以下例。

```js
export default class Test extends LightningElement {
   @api sampleText = ""
}
```

```html
<template>
    <c-test sample-text="サンプル　"></c-test>
</template>
```




## VS Code のプレビューではデフォルトは英語、 toast などの一部機能は使えない


今回、[ociInvRegUtils.js](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/lwc/ociInvRegUtils/ociInvRegUtils.js#L1) でトーストを利用していますが、プレビューでは何も表示されなかったです。確認するためには一度プラットフォームにデプロイする必要がありそうです。
また、今回多言語化も行っていますが、プレビューでは英語のみの表示でした。こちらも他の言語を確認したいときは一度デプロイする必要があります。
その他ローカル開発でのプレビューでサポートされているか確認したい場合は、ヘルプの[サポート対象とサポート対象外のモジュール、コンポーネント、機能](https://developer.salesforce.com/tools/vscode/ja/localdev/lwc-localdev-supported-modules)を参照ください。



## 今のところ VS Code のプレビューで styling hooks は使えなさそう

[このイシュー](https://github.com/forcedotcom/lwc-dev-server-feedback/issues/76)にもあるように現状ベータ版ということもあって、`:host` や `--slds-c-button-brand-color-background` のような [styling hooks](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/55.0/lwc/lwc.create_components_css_custom_properties) は　使えないようです。確認の際は一度デプロイする必要がありました。




## ユーティリティ系の JS はコンポーネントとしてエクスポート


複数のコンポーネントで共通で使用したいスクリプトはコンポーネントとしてエクスポートして使うことができます。
今回の場合、文字をフォーマットするのと、トーストを利用するために下記のような共通スクリプトを作成しました。


[サンプル](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/lwc/ociInvRegUtils/ociInvRegUtils.js)
```js:force-app/main/default/lwc/ociInvRegUtils/ociInvRegUtils.js
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export function showToast(that, title, message, variant) {
    const event = new ShowToastEvent({
        title,
        message,
        variant
    });
    that.dispatchEvent(event);
}

export function printFormat(template, ...arg) {
    if(!template){
        return "";
    }
    for(let i = 0; i < arg.length; i++){
        template = template.replace(`{${i}}`, arg[i]);
    }
    return template
}
```

[サンプル](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/force-app/main/default/lwc/ociInvRegUtils/ociInvRegUtils.js-meta.xml)
```xml:force-app/main/default/lwc/ociInvRegUtils/ociInvRegUtils.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="xmlns=http://soap.sforce.com/2006/04/metadata">
  <isExposed>false</isExposed>
  <apiVersion>55.0</apiVersion>
</LightningComponentBundle>
```

このコンポーネントを呼ぶ際は下記のように `c/~`  のフォーマットでインポートします。

```js
import { showToast, printFormat } from 'c/ociInvRegUtils'
```

その他こどの共有について知りたい場合は、ヘルプの[ JavaScript コードの共有](https://developer.salesforce.com/docs/component-library/documentation/ja-jp/55.0/lwc/lwc.js_share_code)をご覧ください。



## カスタム表示ラベルは勝手にパッケージに追加される

[手動でパッケージを作成](https://developer.salesforce.com/docs/atlas.ja-jp.packagingGuide.meta/packagingGuide/packaging_uploading_unmanaged.htm)する際、コンポーネントに「カスタム表示ラベル」が表示されないんですが、自動で追加されるそうなので、ご安心を。
他のコンポーネントが未管理パッケージでどのような扱いなのか知りたい場合はヘルプの [未管理パッケージで使用可能なコンポーネント](https://developer.salesforce.com/docs/atlas.ja-jp.238.0.packagingGuide.meta/packagingGuide/packaging_unmanaged_components.htm) をご覧ください。



## カスタム表示ラベルを利用している項目は多言語化できる

予めカスタムラベルを作っておいたら、 App ビルダーでのコンポーネントの項目でカスタム表示ラベルを利用しているところには `{!$Label.~}` のフォーマットで多言語化できるそうです。
詳細についてはヘルプの [カスタム表示ラベルを使用した Lightning ページでのコンポーネントのローカライズされた表示ラベルおよび属性値の定義](https://help.salesforce.com/s/articleView?id=release-notes.rn_forcecom_lab_expressions_in_comp_labels.htm&type=5&release=230) をご覧ください。
![image (44).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/ccb0b9de-5744-5c12-9caf-cab3508ebebc.png)




## Apex でオブジェクトの場合は明示的に Schema. をつける

[今回サンプルデータの投入時](https://github.com/SalesforceDevelopersJapan/jp-cc-se/blob/main/packages/OCIInventoryRegistration/scripts/apex/data.apex)に Location というオブジェクトを利用したのですが、同じ名前のクラスで、[System 名前空間にあるクラス](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_system_Location.htm)があり、単に `Location` と入力するとエラー（Error: Invalid constructor syntax, name=value pairs can only be used for SObjects: System.Location）が発生します。
オブジェクトの Location を明示的に示すには `Schema.Location` というように `Schema.~` を前につけてあげることで解決します。


## .forceignore の利用

スクラッチ環境から PULL してくると必要ないコードまでダウンロードしてしまい、次に PUSH するときにそのソースコードがない場合やパッケージを作成してインストールする際にエラー（例えば、The AppMenu called 'AppSwitcher' is standard and cannot be deleted）が発生します。そのため必要ないソースコードは `.forceignore` に記載するとよさそうです。
[参考にしたStackExchange](https://salesforce.stackexchange.com/questions/366564/deployment-error-the-appmenu-called-appswitcher-is-standard-and-cannot-be-de)




## リリース時のコードカバレッジ率は 75% 以上

他の本番環境ではベータ版のパッケージのインストールが許可されていないことが多いです。
そのためベータ版からリリース版へ promote (リリースバージョンへ昇格)する必要があるのですが、その際に promote するバージョンは予め Apex のコードカバレッジが計算されていて、かつ 75% 以上でないといけません。
リリースするバージョンを作成するコマンド `sfdx force:package:version:create` には　 `--codecoverage` をつけてカバレッジが 75% 以上になるようにしましょう。
詳細についてはヘルプの [Code Coverage for Unlocked Packages](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_unlocked_pkg_code_coverage.htm) をご覧ください。


## Connect API のApex テストは特殊

Connect API のメソッドをテストするためには `@IsTest(SeeAllData=true)` をつけて組織のデータに直接アクセスしてテストする必要があります。また一部のメソッドは組織のデータにアクセスできないので、特別にテストデータをセットするメソッドが準備されています。例えば [setTestGetFeedElementsFromFeed](https://developer.salesforce.com/docs/atlas.ja-jp.238.0.apexcode.meta/apexcode/apex_ConnectAPI_ChatterFeeds_static_methods.htm#apex_ConnectAPI_ChatterFeeds_setTestGetFeedElementsFromFeed_1) のようなメソッドです。
詳細については、ヘルプの [ConnectApi コードのテスト](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/connectAPI_TestingApex.htm) をご覧ください。

## パッケージインストール時に作成したコンポーネントに関係ない Apex のコンパイルでエラーになる時がある

パッケージをインストールした際にインストールしようとしたコンポーネントに関係ないApeクラスで `Apex compile failure` のようなコンパイルエラーが発生することがあります。
その場合は、インスート時に「パッケージの Apex のみをコンパイル」を選択するとインストールするパッケージに関係ある Apex のみをコンパイルすることができるのでエラーを防ぐことができます。

![スクリーンショット 2022-10-18 13.42.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/30d90a2e-9a2d-f606-4c9e-21d8b456b08b.png)