Vue3 Composition APIにおいて、Providerパターン(provide/inject)の使い方と、なぜ重要なのか、理解する。

## はじめに

Composition APIで、**Providerパターン(provide/inject)**が、**なぜ重要**なのでしょうか。

Composition APIを使えば「状態やロジック」を**簡単に**コンポーネントの外に切り出すことができます。

<img width="775" alt="スクリーンショット 2020-07-17 15.47.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/ea3e6ad7-0a00-2464-2959-778ef13e6227.png">

しかしながら、Composition APIだけでは「ロジックや状態は切り出せても、**複数コンポーネント間で共有できない**」からです。

Composition APIによって、**荷物をダンボールに梱包はできても**、**運ぶ人がいない状態**です。

<img width="870" alt="スクリーンショット 2020-07-17 17.49.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/281f26cd-401f-0309-d6a2-f635eca9f816.png">


みなさんは、**VueのProviderパターン(provide/inject)って使ったことありますか?**
https://jp.vuejs.org/v2/api/index.html#provide-inject

「ん、Provider？ なにそれ？」って感じですかね。

Providerパターンとは、**「荷物を運んで、受け渡す」**仕組みのことです。

![nimotsu_ukewatashi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/90fa9223-749f-dfe3-04bf-8fdba4c7681a.png)

Providerを使うと、親コンポーネントから子コンポーネントに共通の状態を**簡単に**受け渡すことができます。
状態を渡す先は、子コンポーネントであればどこでも渡すことができます。
Prop渡しと違ってバケツリレーをする必要がありません。

<img width="702" alt="スクリーンショット 2020-07-24 9.36.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/0d085ff4-c2a8-4206-8779-d7b90d6c3677.png">

私は、Providerを使ったことありませんでした。
そもそも、**存在すら知りませんでした。**

私と同じように、**Providerなんて、知らないし、使ったことない人が多い**んじゃないかなと思います。

そこで、Providerの**「使い方」や「なぜ使うのか」**といった内容を、本記事にまとめてみようかと思います。

### Remote.vueがきっかけでProviderを知る

先月行われた[「Remote.vue #1」](https://lapras.connpass.com/event/177974/)に、視聴枠で参加しました。

動画配信URL: https://youtu.be/rx249I9JmNU

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/rx249I9JmNU/0.jpg)](http://www.youtube.com/watch?v=rx249I9JmNU)

**来たるVue3時代**を見据えた発表内容に、**心躍らせました。**
**特に興味深かった**のは、Vue3時代の設計についてフォーカスされていたセッションです。

 - [ぼくのかんがえたさいきょうのVueあーきてくちゃ](https://speakerdeck.com/slont/bokufalsekangaetasaikiyoufalsevueakitekutiya)
 - [終わりゆく Vue 2.x 時代の状態設計の答えと Vue 3 の Provider への期待](https://speakerdeck.com/potato4d/the-last-architecture-of-the-vue-2-dot-x)

[![thumbnail](https://files.speakerdeck.com/presentations/4c2ea15b67834ac08972df7e7c2f15d8/preview_slide_0.jpg?15643088)](https://speakerdeck.com/potato4d/the-last-architecture-of-the-vue-2-dot-x)

登壇者の方から**「Provider」**や**「provide/inject」**という単語を**頻繁に**聞きました。
私は、その時は、**「Providerってナニソレ美味しいの？」**状態でした。
今になってようやく**Providerの重要性**に気づいたので、当記事に内容をまとめています。

この記事が、誰かのお役に立てれば幸いです。

次回「Remote.vue #2」は、7/31に開催みたいです。楽しみですね。
https://lapras.connpass.com/event/179777/

## Providerパターンとは

Vueにおける**「Providerパターン」**とは、**「provide/inject」APIを使用して**、Composition APIで切り出した**「状態やロジック」**を**「コンポーネント間で共有すること」**を指します。

## Vue3時代は、Providerが重要になる

provide/injectは、**Vue2.2**から使用できるAPIになります。
Vue3から新しく搭載された機能かと勘違いしていたのですが、**意外に古くからある機能**なんですね。

https://jp.vuejs.org/v2/api/index.html#provide-inject

> provide / inject

> 　2.2.0 から新規

>型:

>　provide: Object | () => Object
inject: Array<string> | { [key: string]: string | Symbol | Object }

>詳細:

>この 1 組のオプションは、**コンポーネントの階層がどれほど深いかにかかわらず**、それらが同じ親チェーン内にある限り、祖先コンポーネントが、**自身の子孫コンポーネント全てに対する依存オブジェクトの注入役を務めることができる**ようにするために利用されます。React に精通している人は、 React のコンテキストの特徴と非常によく似ていると捉えると良いでしょう。

Providerパターンは、**Vue 2.x時代は影が薄かった**のですが、Vue 3のリリースが迫るにつれ**にわかに注目**されつつあります。

注目される**理由**は、Vue3の**Composition API**と関連があります。

## Composition APIを使えば、「状態とロジック」を切り出せる

Composition APIについては、稚拙ながら以下の記事を書きましたので、合わせてご覧くださると理解が深まるかと思います。

- [なぜ、Vue Composition APIを使うのか、理解する](https://qiita.com/karamage/items/7721c8cac149d60d4c4a)


Composition APIを使えば、**ロジックを再利用**するために、コンポーネントから**「状態とロジック」を切り出す**ことができます。

ここからは、簡単なカウンターアプリのコードを見ながら説明します。

<img width="379" alt="スクリーンショット 2020-07-16 17.17.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/c651db40-5104-16c7-b9b6-defbee205944.png">

このカウンターアプリは、「+」を押すと+1カウントアップされ、「-」を押すと-1カウントダウンするという単純なものです。

<img width="379" alt="スクリーンショット 2020-07-16 17.17.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/2ebbd2d3-6c01-346f-ac49-046f3cad7974.png">
「+」を押したら、カウントアップした

### コンポーネントに「View、状態、ロジック」が混在した状態

上記のカウンターアプリを、VueのComposition APIを使ってSFCファイルに書くと、以下のようになります。

```vue:CounterApp.vue
<template>
  <div>
    <!-- View -->
    <div>{{ state.count }}</div>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive } from "@vue/composition-api"

export default defineComponent({
  setup() {
    // 状態
    const state = reactive<{
      count: number;
    }>({
      count: 0
    })

    // ロジック
    const increment = () => state.count++
    const decrement = () => state.count--

    return {
      state,
      increment,
      decrement,
    }
  }
})
</script>
```

上記のコードの**問題点**として、コンポーネントの中に**「View、状態、ロジック」**が混在しています。

<img width="341" alt="スクリーンショット 2020-07-17 15.29.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/e9275c9c-0041-6408-767b-62bc2f31eafe.png">


コンポーネントが**肥大化**すると、**可読性が下がったり**、**メンテナンスしにくくなったり**、様々な**問題**を引き起こします。

<img width="775" alt="スクリーンショット 2020-07-17 15.53.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/2200663d-d1ac-4af0-5d0b-e074217a068e.png">


そこで、コンポーネントの中から**「状態とロジック」を外に切り出し**ます。
Composition APIを使っているので、**簡単に**外に切り出すことができます。

<img width="775" alt="スクリーンショット 2020-07-17 15.47.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/ea3e6ad7-0a00-2464-2959-778ef13e6227.png">


「状態とロジック」を切り出したコードは、以下のようになります。


### コンポーネントから、「状態とロジック」を外に切り出す

「useCounter」として、「状態とロジック」をコンポーネントの外に定義します。

```ts:composables/use-counter.ts
import { reactive } from "@vue/composition-api"

export default function useCounter() {
  // 状態
  const state = reactive<{
    count: number;
  }>({
    count: 0
  })

  // ロジック
  const increment = () => state.count++
  const decrement = () => state.count--

  return {
    state,
    increment,
    decrement,
  }
}
```

コンポーネント側は、useCounterをimportして、使うだけになります。

```vue:CounterApp.vue
<template>
  <div>
    <div>{{ state.count }}</div>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api"
import useCounter from "@/composables/use-counter"

export default defineComponent({
  setup() {
    // 状態とロジックを外部に切り出せた!
    const { state, increment, decrement } = useCounter()
    return {
      state,
      increment,
      decrement,
    }
  }
})
</script>
```
ほら、**スッキリ**しましたね。


ここまでの説明で、Composition APIを使えば、「状態とロジック」をコンポーネントの外に切り出せることがわかっていただけたかと思います。

続きまして、本題の**Provider**について、説明していこうと思います。

## なぜ、Vue3時代は、Providerが重要になるのか?

結論から申しますと、Composition APIだけでは「ロジックや状態は切り出せても、**共有できない**」からです。

![character_danbo-ru_walk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/41bf46ef-7e3f-5339-7c96-d998630a6cc8.png)


Composition APIによって、**荷物をダンボールに梱包はできた**のですが、**運ぶ人がいない状態**です。
ダンボールの中身を他の部屋の人が使いたいと思っても、使えません。
ダンボールに勝手に手足が生えて、**勝手に配達先まで歩いていってはくれない**のです。

Composition APIを使えば、「ロジックや状態」を切り出して再利用できるようになります。
しかし状態を切り出しても、運び手がいなければ、**その状態を複数コンポーネント間で共有することはできない**のです。

以下のコード見ながら説明します。
複数コンポーネントでの説明のため、先程のカウンターアプリを、**複数コンポーネントに分割**します。

- CounterApp(カウンターアプリの親コンテナ)
    - CounterDisplay(カウンターの値の表示)
    - CounterIncrementButton(カウンターのカウントアップボタン)
    - CounterDecrementButton(カウンターのカウントダウンボタン)


```vue:CounterDisplay.vue
<template>
  <div>{{ state.count }}</div>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api"
import useCounter from "@/composables/use-counter"

export default defineComponent({
  setup() {
    const { state } = useCounter()
    return {
      state,
    }
  }
})
</script>

```

```vue:CounterIncrementButton.vue
<template>
  <button @click="increment">+</button>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api"
import useCounter from "@/composables/use-counter"

export default defineComponent({
  setup() {
    const { increment } = useCounter()
    return {
      increment,
    }
  }
})
</script>
```

```vue:CounterDecrementButton.vue
<template>
  <button @click="decrement">-</button>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api"
import useCounter from "@/composables/use-counter"

export default defineComponent({
  setup() {
    const { decrement } = useCounter()
    return {
      decrement,
    }
  }
})
</script>
```

```vue:CounterApp.vue
<template>
  <div>
    <CounterDisplay />
    <CounterIncrementButton />
    <CounterDecrementButton />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api"
import CounterDisplay from "@/components/CounterDisplay.vue"
import CounterIncrementButton from "@/components/CounterIncrementButton.vue"
import CounterDecrementButton from "@/components/CounterDecrementButton.vue"

export default defineComponent({
  components: {
    CounterDisplay,
    CounterIncrementButton,
    CounterDecrementButton,
  },
  setup() {
    return {}
  }
})
</script>
```

上記のコードは、**残念ながら正常動作しません。**
「+」を押してみても、カウントアップしません。
「-」を押してみても、カウントダウンしません。

<img width="379" alt="スクリーンショット 2020-07-16 17.17.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/8771f442-8159-8667-b537-a6f667ade273.png">

ボタンを押しても反応しなくなった(泣

何が**ダメ**なのでしょうか?

原因は、それぞれのコンポーネントで**useCouterをインポートしているのが原因**です。
CounterDisplayとCounterIncrementButton、CounterDecrementButtonでは、**別の状態**がインポートされます。(インスタンスがコピーされるイメージ)

<img width="870" alt="スクリーンショット 2020-07-17 17.44.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/1bead52a-f63d-ffd7-6d53-fb6df79a76f7.png">


つまり、それぞれの部屋に、ダンボールがあって中身は同じなのですが、**すべてコピー品**なのです。

コンポーネント間で、同じ状態を共有したい場合、**ダンボールを一つ用意して、それを運ぶ**必要があります。

<img width="870" alt="スクリーンショット 2020-07-17 17.49.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/281f26cd-401f-0309-d6a2-f635eca9f816.png">


そう、**この配達人こそProviderなのです!**

![nidai_hakobu_sagyouin_man.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/ad867d57-5ab4-cd27-160f-d6ca7d224533.png)


Providerを使えば、複雑な状態も簡単に管理することができます。

今後は、Composition APIとProviderを組み合わせて使うのがよいです。
Composition APIで「状態とロジックを切り出して」Providerで「運ぶ」って感じです。

![nidai_hakobu_sagyouin_man_box.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/7c5a3657-cdd5-74ff-26dd-b3f0a990bfcd.png)

Providerを使うと、親コンポーネントから子コンポーネントに共通の状態を**簡単に**受け渡すことができます。
状態を渡す先は、子コンポーネントであればどこでも渡すことができます。
Prop渡しと違ってバケツリレーをする必要がありません。

<img width="702" alt="スクリーンショット 2020-07-24 9.36.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/0d085ff4-c2a8-4206-8779-d7b90d6c3677.png">

つまりVue3では**「Provider」+「Composition API」の組み合わせで設計を考えていく必要**があります。

Propsで渡す方法もありますが、コンポーネントツリーが深い場合、遠くのコンポーネントに運ぶのはバケツリレーがかなり大変です。。バケツリレーだと、荷物に関心のない人にも迷惑がかかるのでオススメしません。


##「provide/inject」の使い方

提供する側(provide)と注入される側(inject)に分かれます。

- 送信側(provide)
- 受信側(inject)

実際に「provide/inject」を使ったコードを使って説明します。

まずは、UseCounterKeyを作ります。

これは、荷物にタグ付けするイメージです。

```ts:composables/use-counter-key.ts
import { InjectionKey } from '@vue/composition-api';
import { CounterStore } from './use-counter';

const CounterKey: InjectionKey<CounterStore> = Symbol('CounterStore');
export default CounterKey;
```

use-counterの戻り値について、型情報を追加しておきます。

```ts:composables/use-counter.ts
import { reactive } from "@vue/composition-api"

export default function useCounter() {
  // 状態
  const state = reactive<{
    count: number;
  }>({
    count: 0
  })

  // ロジック
  const increment = () => state.count++
  const decrement = () => state.count--

  return {
    state,
    increment,
    decrement,
  }
}

// 追加
export type CounterStore = ReturnType<typeof useCounter>
```

親コンポーネントであるCounterAppから、useCounterをkeyでタグ付けして、provideします。
**住所を書いて、荷物の配達を依頼するイメージ**です。

```vue:CounterApp.vue
<template>
  <div>
    <CounterDisplay2 />
    <CounterIncrementButton2 />
    <CounterDecrementButton2 />
  </div>
</template>

<script lang="ts">
import { defineComponent, provide } from "@vue/composition-api"
import CounterDisplay2 from "@/components/CounterDisplay2.vue"
import CounterIncrementButton2 from "@/components/CounterIncrementButton2.vue"
import CounterDecrementButton2 from "@/components/CounterDecrementButton2.vue"
import CounterKey from "@/composables/use-counter-key"
import useCounter from "@/composables/use-counter"

export default defineComponent({
  components: {
    CounterDisplay2,
    CounterIncrementButton2,
    CounterDecrementButton2,
  },
  setup() {
    provide(CounterKey, useCounter())
    return {}
  }
})
</script>
```

続いて、**荷物の受け取り側の処理**です。
injectして、荷物を受け取ります。

```vue:CounterDisplay2.vue
<template>
  <div>{{ state.count }}</div>
</template>

<script lang="ts">
import { defineComponent, inject } from "@vue/composition-api"
import { CounterStore } from "@/composables/use-counter"
import CounterKey from "@/composables/use-counter-key"

export default defineComponent({
  setup() {
    const { state } = inject(CounterKey) as CounterStore
    return {
      state,
    }
  }
})
</script>
```

```vue:CounterIncrementButton2.vue
<template>
  <button @click="increment">+</button>
</template>

<script lang="ts">
import { defineComponent, inject } from "@vue/composition-api"
import { CounterStore } from "@/composables/use-counter"
import CounterKey from "@/composables/use-counter-key"

export default defineComponent({
  setup() {
    const { increment } = inject(CounterKey) as CounterStore
    return {
      increment,
    }
  }
})
</script>
```

```vue:CounterDecrementButton2.vue
<template>
  <button @click="decrement">-</button>
</template>

<script lang="ts">
import { defineComponent, inject } from "@vue/composition-api"
import { CounterStore } from "@/composables/use-counter"
import CounterKey from "@/composables/use-counter-key"

export default defineComponent({
  setup() {
    const { decrement } = inject(CounterKey) as CounterStore
    return {
      decrement,
    }
  }
})
</script>
```

<img width="379" alt="スクリーンショット 2020-07-16 17.17.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/6609a281-07bd-ef1a-10c0-4c70cbc2e4c1.png">

これで、ボタンをクリックして**正常にカウントアップできる**ようになりました。

![yubin_ukewatashi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93703/2081258a-1d19-faa8-3d4a-927c3f1aadc1.png)


以上が、Providerの説明でした。

### 今後、ProviderのAPIは変わるかも?

もともとPlugin開発やライブラリ開発向けのAPIとして「provide/inject」が用意されていました。
つまり、「provide/inject」はVue3用に用意されたAPIではないのです。
よって、Providerの仕組みは、**今後、変わるかも**しれません。
「provide/inject」仕組みは、**問題点も多い**からです。
例えばKeyを書き間違えたら不具合を引き起こしますが、それが実行時にしかわからないです。
injectの対象がコンポーネント単位のため、依存が大きいです。(もっと依存度を下げて使いたい)
今後Vue3で改善されると予想しています。

しかし、**抽象レベルの「Providerパターン」の考え方は変わりません**ので、知っておいて損はないかと思います。


## まとめ

- Composition APIを使って、散らかった部屋の荷物を**ダンボールに梱包して、外に**出そう。
- Composition APIを使って**梱包した荷物を運ぶのがProviderの役目**
- Providerで、親コンポーネントから子コンポーネントに共通の状態を**簡単に**受け渡すことができる。
- Prop渡しと違って**バケツリレーをする必要がない。**
- 今後、Vue3では**Providerの改善**が進むと予想

最後まで読んでいただき、ありがとうございました！

## 参考リンク集

- 終わりゆく Vue 2.x 時代の状態設計の答えと Vue 3 の Provider への期待
https://speakerdeck.com/potato4d/the-last-architecture-of-the-vue-2-dot-x

- ぼくのかんがえたさいきょうのVueあーきてくちゃ
https://speakerdeck.com/slont/bokufalsekangaetasaikiyoufalsevueakitekutiya

- Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか？
https://qiita.com/tmy/items/a545e44100247c364a71

- Vue2 API provide/inject
https://jp.vuejs.org/v2/api/index.html#provide-inject

- Vue Composition APIでストアパターンをスマートに使って状態管理をする
https://qiita.com/resessh/items/ab09ec925ca49d02caae

- Vue Composition API + TypeScriptで DI(依存性の注入）, DIP（依存性逆転の原則） を実装してみる
https://qiita.com/ryo2132/items/03380df2df5b4b2933d7