[Vue.js] provide / inject

# prop drilling

コンポーネントを小さく分割して保守性を高めること自体はいいことです。
この時、親コンポーネントから子コンポーネントに、子コンポーネントから孫コンポーネントにpropsでデータを受け渡していく処理は"prop drilling"と呼ばれています。

"prop drilling"を使うと、データが親コンポーネントと子コンポーネント間でのみ依存するのでデバックしやすかったり、特別なことはしていないのでプロジェクトに簡単に統一感を得られたりします。

その一方で、大規模になるにつれてコンポーネントツリーが深くなると、このpropsによるデータの受け渡しが煩雑になります。あるコンポーネントからあるコンポーネントにデータを受け渡したい時、その中間のコンポーネントすべてで親コンポーネントからのpropsの受け入れと子コンポーネントへのprops受け渡しが必要になるわけです。
記述量が増えること、中間コンポーネントは直接使用しないpropsを定義する必要や、リファクタリングの影響が大きくなることが課題に挙げられます。(よくバケツリレー問題などと呼ばれる)
（また、同じような記述が増えてpropsの受け入れ忘れなどもあり得ますね）

# provide / inject とは何か

https://jp.vuejs.org/v2/api/index.html#provide-inject
> この 1 組のオプションは、コンポーネントの階層がどれほど深いかにかかわらず、それらが同じ親チェーン内にある限り、祖先コンポーネントが、自身の子孫コンポーネント全てに対する依存オブジェクトの注入役を務めることができるようにするために利用されます。

Vue.jsにおいて上記の問題を解決する手法の一つとして"provide/inject"があります。
簡単に説明すると親コンポーネント→子コンポーネント→孫コンポーネントがあって、親から孫へデータを受け渡す時、"prop drilling"を用いるとそれぞれのコンポーネントにpropsの受け渡しに関する記述をする必要が生まれますが、"provide/inject"では親コンポーネントにprovideを記述して孫コンポーネントにinjectを記述するだけで済むということです。

# 実装例

以下のようなコンポーネントツリー構造をしているプロジェクトを考えます。

```text
App.vue
┗ HelloWorld.vue // 親
  ┗ MailForm.vue // 子
    ┣ Btn.vue // 孫
    ┗ TxtField.vue // 孫
```

コード例ではvue-cliで作られるサンプルコードを編集して使っています。

## "prop drilling"で記述

```vue:HelloWorld.vue
<template>
  <div class="hello">
    <mail-form
      :placeholder="placeholder"
      :btn-label="btnLabel"
      @clickSubmit="submitHandler"
    />
    <div class="send-msg">結果: {{ sendMsg }}</div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import MailForm from "./normal/MailForm.vue";

@Component({
  components: {MailForm}
})
export default class HelloWorld extends Vue {
  placeholder = "メールアドレスを入力";
  btnLabel = "送信";
  sendMsg = "";

  submitHandler() {
    this.sendMsg = "submitted!";
  }
}
</script>
...

```

```vue:MailForm.vue
<template>
  <div class="form">
    <txt-field :placeholder="placeholder" />
    <btn @btnClick="submitHandler">{{ btnLabel }}</btn>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Prop, Vue } from "vue-property-decorator";
import Btn from "./Btn.vue";
import TxtField from "./TxtField.vue";

@Component({
  components: {
    Btn,
    TxtField
  }
})
export default class MailForm extends Vue {
  @Prop() private placeholder!: string;
  @Prop() private btnLabel!: string;

  @Emit("clickSubmit")
  submitHandler() {
    return;
  }
}
</script>
```

```vue:TxtField.vue
<template>
  <div class="txt-field">
    <input type="text" :placeholder="placeholder" />
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class TxtField extends Vue {
  @Prop() private placeholder!: string;
}
</script>
```

```vue:Btn.vue
<template>
  <div class="btn">
    <button @click="btnHandler"><slot /></button>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Vue } from "vue-property-decorator";

@Component
export default class Btn extends Vue {
  @Emit("btnClick")
  btnHandler() {
    return;
  }
}
</script>
```
placeholderに注目するとHelloWorld→MailForm→TxtFieldとpropsで渡しているのがわかります。

## "provide/inject"で記述

```vue:HelloWorld.vue
<template>
  <div class="hello">
    <mail-form2 @clickSubmit="submitHandler2" />
    <div class="send-msg">結果: {{ sendMsg2 }}</div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Provide, Vue } from "vue-property-decorator";
import MailForm2 from "./provide-inject/MailForm2.vue";

@Component({
  components: {MailForm2}
})
export default class HelloWorld extends Vue {
  @Provide() placeholder2 = "メールアドレスを入力(provide-inject)";
  @Provide() btnLabel2 = "送信(provide-inject)";
  sendMsg2 = "";

  submitHandler2() {
    this.sendMsg2 = "submitted!";
  }
}
</script>
...

```

```vue:MailForm.vue
<template>
  <div class="form">
    <txt-field />
    <btn @btnClick="submitHandler">{{ btnLabel2 }}</btn>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Inject, Vue } from "vue-property-decorator";
import Btn from "./Btn.vue";
import TxtField from "./TxtField.vue";

@Component({
  components: {
    Btn,
    TxtField
  }
})
export default class MailForm2 extends Vue {
  @Inject() btnLabel2!: string;

  @Emit("clickSubmit")
  submitHandler() {
    return;
  }
}
</script>

```

```vue:TxtField.vue
<template>
  <div class="txt-field">
    <input type="text" :placeholder="placeholder2" style="width:250px;" />
  </div>
</template>

<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";

@Component
export default class TxtField extends Vue {
  @Inject() placeholder2!: string;
}
</script>
```

```vue:Btn.vue
<template>
  <div class="btn">
    <button @click="btnHandler"><slot /></button>
  </div>
</template>

<script lang="ts">
import { Component, Emit, Vue } from "vue-property-decorator";

@Component
export default class Btn extends Vue {
  @Emit("btnClick")
  btnHandler() {
    return;
  }
}
</script>
```
HelloWorld（親コンポーネント）でProvideを記述することで子コンポーネントのどこからでもInjectで呼び出すことができるようになりました。
その結果、MailFormからplaceholder2に関する記述をなくすことができました。

# とはいえ推奨はされていない
https://jp.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5
> しかし、依存性の注入には不都合な点があります。 依存性の注入はアプリケーションのコンポーネントを現在の状態に密結合させ、リファクタリングを難しくさせます。そして提供されるプロパティはリアクティブではありません。これは設計上の理由によるものです。中央データストアを作るために依存性の注入を使うことは、同じ目的のために$rootを使うことと同じくらいアプリケーションのスケールを難しくします。もしアプリケーションに特定のプロパティをシェアしたいのなら、もしくはもし先祖に提供したデータを更新したいのなら、そのときは組み込みの機能よりむしろ、Vuexのような本物の状態管理ソリューションを必要とするいい兆候です。