カスタムワイヤーアダプターを作ってみました

この記事では、 オープンソース版 LWC でカスタムワイヤーアダプターを作成し、 @wire デコレータでデータプロビジョンする方法をご紹介します。
[オープンソース版 LWC でカスタム Express サーバーの設定方法](https://qiita.com/hrk623/items/688d98f1a2167593217d) で作成したプロジェクトを編集する前提となっています。

## ワイヤーアダプター
LWC にはデータサービスとしてワイヤーサービスと呼ばれる、LWC コンポーネントで利用するデータを宣言的に取得するための仕組みが備わっています。
ワイヤーサービスを利用してデータを取得する場合は、ワイヤーアダプターをインスタンス化する必要があり、以下のフォーマットで宣言する事になります。

```js
@wire(adapterId, [config]) wiredProperty|wiredFunction
// @wire: wire デコレータ。config の値に変更があれば wireAdapter に紐づく eventTarget に通知する
// adapterId: データ取得ロジックが実装されたアダプター関数への参照
// config: アダプターを起動する設定。通常はデータ取得のためのパラメータ。
// wiredProperty|wiredFunction: ワイヤー変数、もしくはワイヤー関数。ワイヤー値でプロビジョンされる。
```

開発者は独自のアダプターを開発し、`wire` でデコレートする事であらゆるワイヤーアダプターを作成する事が出来ます。 


## 初めてワイヤーサービスを使う場合
オープンソース LWC で新規に作成したプロジェクトの場合、そのままではワイヤーサービスを利用できません。
ワイヤーサービスを使うには、`lwc` モジュールの `register` 関数と `wire-service` モジュールを紐付けておく必要があります。

```js:src/client/index.js
import { createElement, register} from 'lwc';
import MyApp from 'my/app';

// register 関数を wire-service に登録する
import { registerWireService } from 'wire-service';
registerWireService(register);

const app = createElement('my-app', { is: MyApp });
document.querySelector('#main').appendChild(app);
```


## カスタムワイヤーアダプターを使ったサンプル
このサンプルでは、画面上ので選んだ値をもとに、サーバーから顧客情報を取得、画面にテキストで表示する、というシンプルなコンポーネントを作成しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26378/e8eb0d66-c95a-95b2-61f5-745f230682eb.png" width="640" />

```html:src/client/modules/my/app/app.html
<template>
    <select onchange={onSelectChanged}>
        <option value="1">1</option>
        <option value="2">2</option>
    </select>
    <p>{contact}</p>
</template>
```

```js:src/client/modules/my/app/app.js
import {LightningElement, wire} from 'lwc';
import getContact from 'api/getContact';

// Component Class
export default class App extends LightningElement {
    // Private Properties
    contactId;

    // Wired Properties
    @wire(getContact, {contactId: '$contactId'}) _contact;

    // Getters and Setters
    get contact() { 
        return JSON.stringify(this._contact.data); 
    }

    // Callbacks
    renderedCallback() {
        const select = this.template.querySelector('select');
        this.contactId = select.options[select.selectedIndex].value;
    }

    // Event Handlers
    onSelectChanged(e) {
        this.contactId = e.target.value;
    }
}
```

```js:src/client/modules/api/getContact/getContact.js
import { register, ValueChangedEvent } from 'wire-service';

// アダプターとなる関数を定義し、エクスポートする
export default function getContact(config) {
    if (!config.contactId) return Promise.resolve();
    return fetch(`/contact/${config.contactId}`).then(response => response.json());
}

// アダプター関数に紐づくワイヤーアダプターをインスタンス化するためのファクトリー関数を登録する
// ファクトリー関数では、いつ、どんな形式でワイヤー変数/関数をプロビジョンするかを定義しておく
register(getContact, eventTarget => {
    // コンポーネントが接続された時
    eventTarget.addEventListener('connect', () => {
        // ワイヤー変数/関数に初期値をプロビジョンする
        eventTarget.dispatchEvent(new ValueChangedEvent({ data: undefined, error: undefined }));
    });

    // config が変更された時
    eventTarget.addEventListener('config', config => {
        // 新しい config でワイヤー変数/関数をプロビジョンする
        getContact(config)
            .then(contact => eventTarget.dispatchEvent(new ValueChangedEvent({ data: contact })))
            .catch(error => eventTarget.dispatchEvent(new ValueChangedEvent({error: error})))
    });

    // コンポーネントが切断された時
    eventTarget.addEventListener('disconnect', config => {
        // 処理なし
    });
});
```