Vueのprovide/injectで 子コンポーネントで同一キーをprovideしたときの動きについて

※調べたVueのバージョンは3.3.4です。

Vueでは [Provide/Inject](https://ja.vuejs.org/guide/components/provide-inject.html#prop-drilling)の仕組みを利用することで親コンポーネントからprovideした内容を直接の子コンポーネントだけでなく、さらに深い階層の子コンポーネントから取り出す事ができます。

## provide/injectの使い方

provideは 親コンポーネントで

```js
provide("key", "value")
```

または アプリケーションレベルで

```js
import { createApp } from 'vue'

const app = createApp({})

app.provide("key", "value")
```

のようにして行います。


取り出すときはinjectを用いて


```js
const value = inject("key")
```

のように値を取り出して利用します。

## 同一キーでprovideした場合どのようになるのか？

以下のようなコンポーネント階層を考えます。

![image01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/2e268ae1-64c4-bb26-94a2-3c3850310471.png)


この時 Component1でprovideしたものと同一のキーをComponent3Aなどでprovideした場合、
Component4ではどのような値になるか、つまり以下の図のような場合です。

![image02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/dfd37ebd-1b2e-135e-7001-c1a3216aab39.png)


結果としては以下のようになります。
(実験に用いたコードは最後に記載しています。![image03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/b44c9a1d-6ede-bd5a-0155-89ee5ac62a32.png)


Component3A直下のComponent4のvalueAがfrom-Component3Aとなっていることから
同一キーでprovideした場合は、親からprovideした値を上書きできることがわかります。

## なぜこの挙動になるのか？

ここは正確ではない可能性がありますが ソースを追いかけた感じだと

[apiInject_inject](https://github.com/vuejs/core/blob/v3.3.4/packages/runtime-core/src/apiInject.ts#L59) を見てみるとinject関数では自信の親の`provides` から取得を行っているようです。

`provides` は[createComponentInstance](https://github.com/vuejs/core/blob/v3.3.4/packages/runtime-core/src/component.ts#L484) で親コンポーネント、なければアプリケーションの`provides`をそのままセットしているようです。


このままだと injectしたときはルートとなる親などでprovideしたものがそのまま利用されそうですが
[apiInject_provide](https://github.com/vuejs/core/blob/v3.3.4/packages/runtime-core/src/apiInject.ts#L27)を見てみると

```ts
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
    if (parentProvides === provides) {
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
```

自身のprovidesと親のprovidesが同一の場合は、親のprovidesをprototypeにもつ新しいprovidesを作成し自身のprovidesとしているようです。

これがあることで自身のコンポーネント配下で疑似的なスコープを作ることができるようです。

## 最後に

同一キーをprovideしたときについて公式ドキュメントでは言及されていませんでしたが

ソースコードを見た限りだと特に問題なく利用することができそうです。

ただし、この挙動については公式ドキュメントに記載がないため将来的に変更される可能性があることも意識しておいたほうがよいかもしれません。

## 使用したコード


### Componnet1

valueA, valueBをprovide

```vue:Component1.vue
<script setup>
import { provide, ref } from "vue";
import Component2 from "./Component2.vue"

const valueA = ref("from-Component1")
const valueB = ref("from-Component1")

provide("valueA", valueA)
provide("valueB", valueB)
</script>

<template>
  <div class="component1">
    <div>Component1</div>
    <Component2/>
  </div>
</template>
```


### Componnet2

valueA, valueBをinjectして表示

```vue:Component2.vue
<script setup>
import { inject } from "vue";
import Component3A from "./Component3A.vue"
import Component3B from "./Component3B.vue"

const valueA = inject("valueA")
const valueB = inject("valueA")
</script>

<template>
  <div class="component2">
    <div>Component2</div>
    <div>
      valueA: {{ valueA }}
    </div>
    <div>
      valueB: {{ valueB }}
    </div>
    <div>
      <Component3A/>
      <Component3B/>
    </div>
  </div>
</template>
```

### Componnet3A

valueAのみprovideする。

```vue:Component3A.vue
<script setup>
import { provide, ref } from "vue";
import Component4 from "./Component4.vue"

const valueA = ref("from-Component3A")

provide("valueA", valueA)
</script>

<template>
  <div class="component3A">
    <div>Component3A</div>
    <Component4/>
  </div>
</template>
```

### Componnet3B
何もprovideしない

```vue:Component3B.vue
<script setup>
import Component4 from "./Component4.vue"

</script>

<template>
  <div class="component3B">
    <div>Component3B</div>
    <Component4/>
  </div>
</template>
```

### Componnet4

valueA, valueBをinjectして表示

```vue:Component4.vue
<script setup>
import { inject } from "vue";

const valueA = inject("valueA")
const valueB = inject("valueB")
</script>

<template>
  <div class="component4">
    <div>Component4</div>
    <div>
      valueA: {{ valueA }}
    </div>
    <div>
      valueB: {{ valueB }}
    </div>
  </div>
</template>
```