※調べたVueのバージョンは3.3.4です。
Vueでは Provide/Injectの仕組みを利用することで親コンポーネントからprovideした内容を直接の子コンポーネントだけでなく、さらに深い階層の子コンポーネントから取り出す事ができます。
provide/injectの使い方
provideは 親コンポーネントで
provide("key", "value")
または アプリケーションレベルで
import { createApp } from 'vue'
const app = createApp({})
app.provide("key", "value")
のようにして行います。
取り出すときはinjectを用いて
const value = inject("key")
のように値を取り出して利用します。
同一キーでprovideした場合どのようになるのか?
以下のようなコンポーネント階層を考えます。
この時 Component1でprovideしたものと同一のキーをComponent3Aなどでprovideした場合、
Component4ではどのような値になるか、つまり以下の図のような場合です。
結果としては以下のようになります。
(実験に用いたコードは最後に記載しています。
Component3A直下のComponent4のvalueAがfrom-Component3Aとなっていることから
同一キーでprovideした場合は、親からprovideした値を上書きできることがわかります。
なぜこの挙動になるのか?
ここは正確ではない可能性がありますが ソースを追いかけた感じだと
apiInject_inject を見てみるとinject関数では自信の親のprovides
から取得を行っているようです。
provides
はcreateComponentInstance で親コンポーネント、なければアプリケーションのprovides
をそのままセットしているようです。
このままだと injectしたときはルートとなる親などでprovideしたものがそのまま利用されそうですが
apiInject_provideを見てみると
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
<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して表示
<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する。
<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しない
<script setup>
import Component4 from "./Component4.vue"
</script>
<template>
<div class="component3B">
<div>Component3B</div>
<Component4/>
</div>
</template>
Componnet4
valueA, valueBをinjectして表示
<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>