
ブログをNext.js14(App Router)でリファクタリングしました
Vueにおいてコンポーネントの階層が深くなったとき、浅い階層から深い階層へデータを渡すためにバケツリレー(props drilling)が発生することがあります。そのために、データを一元管理するVuexやPiniaなどの外部ライブラリを使うことが一般的です。しかしアプリの規模が小さい場合や外部ライブラリがオーバースペックになる場合は、Vueに備えられているProvideとInjectを利用する手もあります。この記事ではVue3におけるProvideとInjectをComposition APIとTypeScriptで解説していきます。
目次
出来ることはとてもシンプルで、親で定義したデータを”バケツリレー(props drilling)しないで”子孫コンポーネントに直接渡せます。
VuexやPiniaなどの状態管理用の外部ライブラリほど複雑なことは出来ません。
親コンポーネントにおいて、子孫に供給したいデータをprovide関数に渡します。
その際に、データを一意に識別するキーを第1引数に指定します。
<script setup lang="ts">
import { provide } from "vue"
provide(キー, 渡したいデータ)
</script>
供給されたデータを使いたい子孫コンポーネントは、inject関数でデータを受け取ります。
その際に親で指定したキーを引数に渡します。
<script setup lang="ts">
import { inject } from "vue"
const useData = inject(キー)
</script>
キーに対応するデータがない場合に備えて、injectの第2引数にはデフォルト値を渡すことも可能です。
inject(キー, デフォルト値)
TypeScriptを用いた場合、キーの指定方法は2パターンあります。
の2パターンです。どちらを使うかによって型指定やデータの受け取り方が異なるので、順番に解説していきます。
文字列キーとは、文字通り「string型のキー」を使用する方法です。
次のようにして使います。キーの名前は任意です。
//"key1"という文字列キーにnumber型の30を渡す
provide("key1", 30)
//"key2"という文字列キーにstring型の"hoge"を渡す
provide("key2", "hoge")
provideに渡すデータの型を明示的に指定していませんが、この時点でTypeScriptによって自動的に型推論が行われています。
推論される型よりも詳細に型を指定したい場合など、明示的に型を指定したい場合はジェネリックで渡します。
// string | number型のデータ
provide<string | number>("fuag", hoge)
// number[]型のデータ
provide<number[]>("fuga", fuga)
データを供給する親コンポーネント側は以上です。
データを受け取るにはinject関数では、ジェネリックで明示的に型の指定をする必要があります。
明示しないとunknown型になってしまいます。
const useKey1 = inject<number>("key1")
const useKey2 = inject<string>("key2")
InjectionKeyというものをimportすることで、キー自体に型の定義を含めることが可能です。
次のように定義して利用します。
import { InjectionKey } from "vue"
const key: InjectionKey<渡したいデータの型> = Symbol()
コードの通り、InjectionKeyの正体はSymbol型です。
キーにデータの型が含まれているので、provideには型指定がいりません。
provide(key, hoge)
InjectionKeyはSymbol型なので、このキーは唯一無二です。
つまり、別ファイルで同じコードを記述したところで別のものとなってしまいます。
よって、InjectionKeyを定義したファイルからexportして利用します。
script setup構文を用いている場合、exportが使えないので次のように分割して記述する必要があります。
<script lang="ts">
import { InjectionKey } from "vue";
export const key: InjectionKey<string> = Symbol()
</script>
<script setup lang="ts">
import { provide } from "vue"
provide(key, "hoge")
</script
データを受け取るコンポーネントでは、親でexportしたキーをimportしてinject関数に渡すだけです。
キーに型定義が含まれているので、inject関数で型を定義する必要はありません。
よって、シンプルに次のようにして記述します。
const useKey = inject(key)
文字列キーよりもInjectionKeyを利用したほうがTypeScriptの恩恵を受けることができますし、バグの少ないコードが書けます。
というのも、文字列キーを利用した場合は次の点が問題になるからです。
InjectionKeyを利用すれば、キーはimportして利用するので(エディタの機能によって)キーのタイプミスに気づくことが可能です。
また、キー自体に型の定義が含まれているので、injectを利用する側は型の定義を予め知る必要がありません。
マストではありませんが、極力変更しないことが推奨されています。
どうしても変更する必要がある場合、次のように、リアクティブなデータを変更する責務を持つメソッドを親から提供します。
親コンポーネント
// Component A
<script lang="ts">
import { InjectionKey, Ref } from "vue";
export const numKey: InjectionKey<Ref<number>> = Symbol('num')
export const updateNumKey: InjectionKey<(n: number) => void> = Symbol('updateNum')
</script>
<script setup lang="ts">
import ComponentB from "./ComponentB.vue"
import { provide, ref } from "vue"
//リアクティブデータ
const num = ref(0)
//リアクティブデータの更新メソッド
const updateNum = (n: number) => num.value = n
provide(numKey, num) // リアクティブデータのprovide
provide(updateNumKey, updateNum) //メソッド自体をprovide
</script>
子孫コンポーネント
// Component B
<script setup lang="ts">
import { numKey, updateNumKey } from "./ComponentA.vue"
import { inject } from "vue"
const useNum = inject(numKey)
const useUpdateKey = inject(updateNumKey)
// 何らかのイベントでリアクティブデータの変更
const onClick = () => useUpdateKey?.(2)
</script>
また、子孫での変更を防ぐために、provideする際にはreadonlyをつけることが推奨されています。
import { readonly, provide } from "vue"
provide(key, readonly(hoge))
Vue3において、ProvideとInjectをComposition APIとTypeScriptで解説しました。また、provideでのキー指定の方法は2種類あり、InjectionKeyを利用したほうがよりTypeScriptの恩恵を受けて記述できることも説明しました。このようにProvideとInjectでpropsのバケツリレーを簡単に防ぐことが可能です。かといって多様は禁物で、きちんとコンポーネントや状態管理の設計をして、コンポーネントの責務を考えた上で利用すべきです。
最新の記事
カテゴリー一覧
アーカイブ