Provide / Inject
このページは、すでにコンポーネントの基礎を読んでいることを前提にしています。初めてコンポーネントに触れる方は、まずそちらをお読みください。
props のバケツリレー(Prop Drilling)
通常、親コンポーネントから子コンポーネントにデータを渡す必要がある場合、propsを使用します。ですが、大きなコンポーネントツリーがあり、深くネストされたコンポーネントが遠い祖先のコンポーネントから何かしらを必要とするケースを想像してみてください。props だけを使う場合、親コンポーネントのチェーン全体に同じ props を渡さなければなりません:
<Footer>
コンポーネントはこれらの props を全く気にしないかもしれませんが、<DeepChild>
がそれらにアクセスできるように宣言して渡す必要があることに注意してください。さらに長い親チェーンがある場合、より多くのコンポーネントが影響を受けることになります。これは "props のバケツリレー(Prop Drilling)" と呼ばれていて、対処するのが楽しいことでないことは明らかです。
props のバケツリレーは provide
と inject
で解決できます。親コンポーネントは、そのすべての子孫コンポーネントに対して 依存関係を提供するプロバイダー(dependency provider) として機能できます。子孫ツリー内のどのコンポーネントも、その深さに関係なく、親チェーン内の上位コンポーネントが提供する依存関係を注入(inject) できます。
Provide
コンポーネントの子孫にデータを提供するには provide()
関数を使います:
vue
<script setup>
import { provide } from 'vue'
provide(/* key */ 'message', /* value */ 'hello!')
</script>
<script setup>
を使わない場合、setup()
内で provide()
が同期的に呼び出されていることを確認してください:
js
import { provide } from 'vue'
export default {
setup() {
provide(/* key */ 'message', /* value */ 'hello!')
}
}
provide()
関数は 2 つの引数を受け付けます。第 1 引数はインジェクションキーと呼ばれ、文字列または Symbol
となります。インジェクションキーは、子孫のコンポーネントが、インジェクション(注入)に必要な値を探すのに使われます。1 つのコンポーネントが異なる値を提供するために、異なるインジェクションキーで provide()
を複数回呼び出すことができます。
第 2 引数は提供される値です。この値は refs のようなリアクティブな状態を含む、任意の型にできます:
js
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
リアクティブな値を提供することで、提供された値を使用する子孫コンポーネントが、プロバイダーコンポーネントとのリアクティブな接続を確立できます。
アプリケーションレベルの Provide
コンポーネント内だけでなく、アプリケーションレベルでデータを提供することも可能です:
js
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* key */ 'message', /* value */ 'hello!')
アプリケーションレベルの Provide は、アプリケーションでレンダリングされるすべてのコンポーネントで利用可能です。これは特にプラグインを書くときに便利です。プラグインは通常、コンポーネントを使って値を提供できないからです。
Inject
祖先コンポーネントが提供するデータを注入するには inject()
関数を使用します:
vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
提供された値が ref である場合、そのまま注入され、自動的にアンラップされることはありません。これにより、インジェクターコンポーネントはプロバイダーコンポーネントとのリアクティビティーの接続を保持できます。
リアクティビティーのある provide と inject の完全なサンプル
繰り返しますが、もし <script setup>
を使用しないのであれば、inject()
は setup()
の内部でのみ同期的に呼び出す必要があります:
js
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
インジェクションのデフォルト値
デフォルトでは、inject
は注入されるキーが親チェーンのどこかで提供されることを想定しています。キーが提供されていない場合、実行時に警告が表示されます。
インジェクトされたプロパティをオプションのプロバイダーで動作させたい場合は、コンポーネント props と同様にデフォルト値を宣言する必要があります:
js
// もし "message" にマッチするデータがなかった場合は、
// `value` は "default value" になります
const value = inject('message', 'default value')
場合によっては、関数を呼び出したり、新しいクラスをインスタンス化したりして、デフォルト値を作成する必要があるかもしれません。オプションの値が使用されないケースで不要な計算や副作用を避けるために、デフォルト値を作成するためのファクトリー関数を使用できます:
js
const value = inject('key', () => new ExpensiveClass(), true)
3 番目の引数は、デフォルト値をファクトリー関数として扱うべきことを示します。
リアクティビティーの利用
リアクティブな値を provide / inject する場合、可能な限り、リアクティブな状態への変更を provider の内部で維持することが推奨されます。これは、提供されるステートとその可能な変更が同じコンポーネントに配置されることを保証し、将来のメンテナンスをより容易にしてくれます。
インジェクターコンポーネントからデータを更新する必要がある場合があります。そのような場合は、状態の変更を担当する関数を提供することをおすすめします:
vue
<!-- プロバイダーコンポーネント内部 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
vue
<!-- インジェクターコンポーネント内部 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
最後に、provide
を通して渡されたデータがインジェクターコンポーネントによって変更されないようにしたい場合は、提供された値を readonly()
でラップできます。
vue
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
シンボルキーの利用
今までの例では、文字列のインジェクションキーを使っていました。もしあなたが多くの依存関係を提供するプロバイダーを持つ大規模なアプリケーションで作業していたり、他の開発者が使用する予定のコンポーネントを作成している場合は、衝突の危険性を避けるためにシンボルインジェクションキーを使用するのがベストです。
シンボルは専用のファイルに書き出しておくことをおすすめします:
js
// keys.js
export const myInjectionKey = Symbol()
js
// プロバイダーコンポーネント内
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* 提供するデータ */
})
js
// インジェクターコンポーネント内
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)