Data Provider Component という方法

正式名称が何かわからないのですが、[こちらのサイト](https://vuedose.tips/tips/data-provider-component-in-vue-js/)では `Data Provider Component` という名前になっていたので、そう呼びます。

使う場面があったので、備忘として残します。

## 何ぞや？
`render`関数でスロットにデータを渡すことで、UIを持たず、データやロジックを提供するコンポーネントを定義することができます。

## 例1

`Data Provider Component` 側

``` vue:DateTimeProvider.vue
<script>
export default {
  name: "DateTimeProvider",
  data() {
    return {
      timerId: -1,
      dateTime: new Date()
    };
  },
  mounted() {
    //定期的に時刻を更新
    this.timerId = setInterval(() => {
      this.dateTime = new Date();
    }, 500);
  },
  beforeDestroy() {
    //タイマー停止
    clearInterval(this.timerId);
  },
  render() {
    //$scopedSlotsの中には各slotが含まれている、またスロットは関数になっている。
    //ここからdefaultスロットを用いる、その際slotへのパラメータを渡す事ができる。
    const defaultSlot = this.$scopedSlots.default({
      dateTime: this.dateTime
    });
    //defaultSlotは配列なのでこれをそのまま描画する。
    //こうすることで,このエレメントをdivなどでラップする必要がなくなる。
    //ただしできるのは単一のエレメントだけ
    return defaultSlot[0];
  }
};
</script>
```

利用する側のコード

```vue:Sample01.vue
<template>
  <DateTimeProvider v-slot="{dateTime}">
    <!-- 直下に含むことができるのは１つだけ -->
    <div>
        <div>Date:{{dateTime}}</div>
        <div>LocaleString:{{dateTime.toLocaleString()}}</div>
        <div>秒:{{dateTime.getSeconds()}}</div>
    </div>
  </DateTimeProvider>
</template>

<script>
import DateTimeProvider from "./DateTimeProvider.vue";
export default {
  components: {
    DateTimeProvider
  }
};
</script>
```

受け取った`dateTime` を3通りの方法で表示している。

これを実行すると画像のような挙動をします。
![sample01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/15192176-2346-f1fb-6e74-40094e43d886.gif)

時刻が更新されていることが確認できると思います。

### ポイント
- 現在日付を提供するコンポーネントはスロットに`dateTime`を渡す
- 利用する側のコンポーネントは`v-slot` で `dateTime`を受け取って加工、表示している。

に分けることができます。 利用する側のコンポーネントには現在日付という状態を持つ必要がなくなります。

## 例2

コードを入力したら名称をとってくるようなパターンです。

私も今回このパターンで使いました。

`Data Provider Component` 側

```vue:ProductNameProvider.vue
<script>
//コード,名称のマップ
const products = new Map();
products.set("001", "みかん");
products.set("002", "りんご");
products.set("003", "ぶどう");

export default {
  name: "ProductNameProvider",
  props: {
    productCode: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      productName: ""
    };
  },
  methods: {
    async fireProduct() {
      await this.$nextTick();
      if (this.productCode === "") {
        this.productName = "";
        return;
      }
      //今回は例なので外部apiからではなくMapから名称を取得している。
      this.productName = products.get(this.productCode) || "(Not Found)";
    }
  },
  render() {
    return this.$scopedSlots.default({
      productName: this.productName
    })[0];
  }
};
</script>

```

利用する側のコード

```vue:Sample02.vue
<template>
  <ProductNameProvider ref="productNameProvider" :productCode="productCode" v-slot="{productName}">
    <div>
      <input type="text" v-model="productCode" maxlength="3" @change="onChangedProductCode" />
      <span>:{{productName}}</span>
    </div>
  </ProductNameProvider>
</template>

<script>
import ProductNameProvider from "./ProductNameProvider.vue";
export default {
  components: {
    ProductNameProvider
  },
  data() {
    return {
      productCode: ""
    };
  },
  methods: {
    onChangedProductCode() {
      this.$refs.productNameProvider.fireProduct();
    }
  }
};
</script>
```

`productCode` を変更したときに`ProductNameProvider` の`fireProduct` 関数を実行しています。


これを実行すると画像のような挙動をします。
![sample02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/7865d189-35ab-4cb9-af04-56afc05a23ab.gif)

コード変更したときに名称を取得していることが確認できます。

この例では`@change` で明示的に 呼び出していますが `ProductNameProvider` 側で`watch` する方法でも可能でしょう。

### ポイント
- 名称の取得する方法を`ProductNameProvider.vue` に隠ぺいすることができる。
- 利用する側は`productCode` だけ持てばよく `productName` を`data` に定義する必要がない。
- 実際に名称をどのように表示するかは 利用する側のコンポーネントで自由に決定できる。
  - この例ではすぐ横に表示しているが、下に出したり上に出したりは利用側の自由

## まとめ
- データを提供するコンポーネントを定義する。
- 利用する側は`v-slot` でデータを受け取る、そのことで変数を減らすことができる。
- 受け取ったデータを、どう加工するか？、どう表示するか？は利用する側で決定することができる。

活躍する場面はそんなにあるかわからないですが、覚えておくと便利なテクニックだと思います。


## 参考

- [Data Provider component in Vue.js](https://vuedose.tips/tips/data-provider-component-in-vue-js/)