Provide / Inject - 深い子孫へ文脈依存を直接渡す仕組み
ロードマップ: Vue.js学習ロードマップ
What(何についてか)
Provide / Inject は、祖先コンポーネントが依存値を提供し、その子孫コンポーネントが深さに関係なく直接受け取るための仕組みである。
props が親から子へ明示的に値を渡す契約であるのに対し、Provide / Inject はコンポーネントツリー内の文脈依存を共有する仕組みである。
Why(なぜ必要か)
大きなコンポーネントツリーでは、深い子孫コンポーネントが祖先の値を必要とする場合がある。 props だけでこれを実現すると、中間コンポーネントがその値を使わないにもかかわらず、受け渡しのためだけに props を宣言し続ける必要がある。
この問題を prop drilling と呼ぶ。
Provide / Inject により、
- 中間コンポーネントの責務を汚さない
- 深い子へ必要な依存だけを直接渡せる
- theme, locale, form context, tabs state などの共有文脈を自然に表現できる
How(どう動くか)
1. Provide
祖先コンポーネントは provide(key, value) で依存を配布する。
<script setup>
import { provide } from 'vue'
provide('message', 'hello!')
</script>- 第1引数は injection key
- 第2引数は提供する値
- 値は文字列、オブジェクト、関数、ref など任意の型を取れる
ref を provide すると、inject 側と reactive な接続を保てる。
<script setup>
import { ref, provide } from 'vue'
const count = ref(0)
provide('count', count)
</script>2. App-level Provide
コンポーネント単位ではなく、アプリ全体に対して依存を提供することもできる。
import { createApp } from 'vue'
const app = createApp({})
app.provide('message', 'hello!')これは plugin やアプリ全体の config / service 共有に向く。
3. Inject
子孫コンポーネントは inject(key) で値を受け取る。
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>同じ key を複数祖先が provide している場合は、最も近い祖先の値が使われる。
flowchart TD A["Provider A\nprovide('theme', 'light')"] --> B["Intermediate"] B --> C["Provider C\nprovide('theme', 'dark')"] C --> D["DeepChild\ninject('theme')"]
この場合、DeepChild が受け取るのは dark である。
4. Missing Inject と Default Values
provide が存在しない key を inject すると、通常は undefined になり warning が出る。
エラーとして即 throw されるわけではない。
const value = inject('missing')optional な依存であれば default 値を指定できる。
const value = inject('message', 'default value')生成コストの高い既定値は factory function として遅延生成できる。
const value = inject('key', () => new ExpensiveClass(), true)5. KVS 的だが、ツリーにスコープされた DI である
Provide / Inject は key で値を登録・参照するため、KVS のように見える。 ただし本質は保存機構ではなく、ツリーにスコープされた dependency injection である。
特徴は以下。
- グローバルではなく provider の subtree に閉じる
- 同じ key が複数あれば nearest provider が勝つ
- 値として state, service, function, config を共有できる
Working with Reactivity
1. ref はそのまま inject される
provide された値が ref の場合、inject 側では自動 unwrap されず、そのまま ref として受け取る。 これにより provider と injector の reactive なつながりが維持される。
2. mutation は provider 側に寄せる
Vue 公式は、共有 state の変更責務を可能な限り provider 側へ寄せることを推奨している。 state と mutation を同じ場所に閉じ込めたほうが、保守性が高くなるためである。
推奨パターンは、状態と更新関数を一緒に provide すること。
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script><script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>この設計により、inject 側は変更依頼を出すだけになり、shared state のルールが provider に集約される。
3. readonly による防御
inject 側から直接変更されたくない場合は readonly() を使う。
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>これは「子では読み取りだけが理想」という設計を API レベルで強制できる。
Symbol Keys
小規模では string key でもよいが、大規模アプリや共有部品、plugin では Symbol key が推奨される。 理由は string key の衝突回避である。
export const myInjectionKey = Symbol()// provider
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* data */
})// injector
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)magic string ではなく一意な識別子を共有することで、安全な dependency 解決ができる。
props / provide-inject / composable / store の役割分担
| 仕組み | 主用途 |
|---|---|
| props / emits | 近い親子間の明示的なデータ入出力 |
| provide / inject | 深い子へ親文脈を共有する局所的な DI |
| composable | ロジック再利用 |
| store (Pinia など) | アプリ横断の共有状態 |
Provide / Inject は万能ではなく、「深い子へ、親文脈を局所共有する」用途に最も向いている。
Options API → Composition API 差分(補足)
| 項目 | Options API(旧) | Composition API(新) |
|---|---|---|
| provide / inject 呼び出し位置 | provide / inject option, setup() | script setup 内で直接使用 |
| reactivity の扱い | data() 値の provide は自動では reactive link にならない場合がある | ref / reactive をそのまま provide しやすい |
| ロジック共有との関係 | component option ベースで分散しやすい | composable と役割を分離しやすい |
Key Concepts
| 用語 | 説明 |
|---|---|
| prop drilling | 深い子へ値を渡すために中間コンポーネントが不要な props 中継をする問題 |
| provide | 祖先が依存値を subtree に配布すること |
| inject | 子孫が祖先の依存値を key で受け取ること |
| app-level provide | アプリ全体へ依存値を配布する仕組み |
| nearest provider wins | 同じ key が複数ある場合、最寄り祖先が優先される原則 |
| default value | provider 不在時の既定値 |
| readonly | inject 側からの mutation を防ぐラッパ |
| Symbol key | key 衝突を防ぐための一意な識別子 |