LWC のワイヤーサービスとワイヤーアダプター

今回は少し LWC の中身に踏み込んで、LWC のデータサービスについて調べてみました。
`@wire` デコレータの使い方や仕組みをより深く理解したい方や、カスタムワイヤーアダプターを作成してみたい方向けです。

##LWC が提供するデータサービス
 [LWC の RFC](https://github.com/salesforce/lwc-rfcs/blob/master/text/0000-data-service.md) には、 LWC がフレームワークとして提供すべきデータサービスの規約が明記されています。その中には以下のような内容が含まれています。

 - データを扱う LWC はランタイムでも高速に動作する事
 - LWC の開発とライフサイクルが簡潔である事
 - Aura と LWC のデータサービスに互換性がある事
 - キャッシュされているデータと表示されているデータに一貫性が保たれている事

これらの規約に則り、LWC ではデータサービスとして LWC コンポーネントがデータを取得するための仕組みである「ワイヤーサービス」を提供しています。


##ワイヤーサービス
ワイヤーサービスとは、LWC コンポーネントとデータソースを宣言的につなげる仕組みのことで [@lwc/wire-service](https://www.npmjs.com/package/@lwc/wire-service) パッケージとして公開されています。開発者は、`@wire` デコレータによりデータの取得を宣言的に行えるようになります。

```js:宣言的なデータ取得の例
@wire(getRecord, { id: '$recordId' }) record;
```

`@wire` デコレータは以下の形式で宣言されます。

```js:ワイヤーサービスの記法
@wire(adapterId, [adapterConfig]) wiredProperty|wiredMethod;
// adapterId: データの取得、管理、プロビジョンする関数の識別子
// adapterConfig: (省略可)オブジェクト型のアダプター設定情報
// wiredProperty|wiredMethod: プロビジョンされる変数、もしくは関数
```

##ワイヤーアダプター
ワイヤーアダプターとは、ワイヤーサービスによって呼び出され、データの取得、管理、プロビジョンを行うのプラグイン関数です。

Lightning プラットフォーム上で動作する LWC コンポーネントの場合、[lightning/ui*Api Wire Adapters and Functions](https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.reference_ui_api) にあるワイヤーアダプターが利用可能ですが、オープンソース版 LWC コンポーネントではカスタムワイヤーアダプターを作成する必要があります。

##カスタムワイヤーアダプターの実装サンプル
ワイヤーサービスを利用しデータを取得する場合、独自のワイヤーアダプターを作成する事ができます。[salesforce/lwc](https://github.com/salesforce/lwc) には[カスタムワイヤーアダプターの作成例](https://github.com/salesforce/lwc/blob/master/packages/%40lwc/wire-service/playground/x/todoApi/todoApi.js)が含まれています。以下はその一部抜粋と説明を追加したものです。

```js:todoApi.jsの概要
import { register, ValueChangedEvent } from 'wire-service';
import getObservable from './todo';

// データを取得する関数の実装
// 通常は XHR や Fetch API によりサーバーからデータを取得する処理を記述する
// 必ず Promise を返し、resolve/reject する値は読み取り専用 Proxy とする
export function getTodo(config) {
    return new Promise((resolve, reject) => {
        const observable = getObservable(config);
        ...
        observable.subscribe({
            next: value => resolve(value),
            ...
        });
    });
}

// getTodo をワイヤーアダプターとして登録する事で、@wire(getTodo) 出来るようになる。
// 引数の wiredEventTarget には 3 つのイベントが発生するため適宜データを取得/再取得する
// - config: @wire の adapterConfig に変更が発生した時
// - connect: コンポーネントが接続した時
// - disconnect: コンポーネントが切断された時
// データを取得した際、ValueChangedEvent を発火する事で、
// wiredProperty もしくは wiredMethod をプロビジョンしてくれる
register(getTodo, function getTodoWireAdapter(wiredEventTarget) {
    let subscription;
    let config;

    wiredEventTarget.dispatchEvent(new ValueChangedEvent({ data: undefined, error: undefined }));

    const observer = {
        next: data =>
            wiredEventTarget.dispatchEvent(new ValueChangedEvent({ data, error: undefined })),
        ...
    };

    wiredEventTarget.addEventListener('connect', () => {
        const observable = getObservable(config);
        if (observable) {
            subscription = observable.subscribe(observer);
        }
    });

    wiredEventTarget.addEventListener('disconnect', () => {
        subscription.unsubscribe();
    });

    wiredEventTarget.addEventListener('config', newConfig => {
        config = newConfig; // リアクティブプロパティは値として解決済み
        ...
        const observable = getObservable(config);
        if (observable) {
            subscription = observable.subscribe(observer);
        }
    });
});

```


##カスタムワイヤーアダプターの実装サンプルを動かしてみる
カスタムワイヤーアダプターの実装サンプルは、ローカルサーバーで実行出来る形で公開されているので、クローンすれば実際に動作を確認する事が出来ます。

#####動作環境の確認
- Github からソースを取得後にビルドする必要があるのですが、バージョンに制約があります。以下は私が動作確認したバージョンです。

```bash
$ node -v
> 10.16.0
$ npm -v
> 6.9.0
$ yarn -v
> 1.17.3
```

#####ソースコードの取得とビルド
- まずはリポジトリ全体をクローンし、LWC をビルドします。

```bash
$ git clone https://github.com/salesforce/lwc.git
$ cd lwc
$ yarn
```

#####カスタムワイヤーアダプターの動作を確認
- ワイヤーサービスのパッケージ内にカスタムワイヤーアダプターのサンプルがあるので、ローカル環境で動作させてみます。

```bash
$ cd packages/@lwc/wire-service
$ yarn start
$ open http://localhost:3000
```

- ブラウザに表示されるのはこんな画面
    - `Load a todo by id` の値を変更すると、その変更に応じて表示内容が変わります。
    - カスタムワイヤーアダプターに対し複数パターンの呼び出しサンプルがあるため、それぞれの動作を比較出来ます。
 
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26378/57211a1e-376d-0e67-e6b1-5c7977322e24.png" width="480" />

##参考
- [Github: Data Service](https://github.com/salesforce/lwc-rfcs/blob/master/text/0000-data-service.md)
- [Github: Wire Service](https://github.com/salesforce/lwc/tree/master/packages/%40lwc/wire-service)
- [npm: @lwc/wire-service](https://www.npmjs.com/package/@lwc/wire-service)