defineExpose と Provide & Inject の違いについて

データなどを共有したり、公開することができる defineExpose と Provide & Inject の違いについてまとめる。

❓ defineExpose について

defineExpose は、Vue.js のコンパイラーマクロで子コンポーネントの script setup 構文で定義されたプロパティを親コンポーネント、もしくは外部のスクリプトファイルに対して公開する機能です。

script setup 構文は、デフォルトでスコープが閉じているため、親コンポーネントや外部のスクリプトファイルは通常アクセスすることができません。

そのため、親コンポーネントから子コンポーネントの変数にアクセスしたい場合などは defineExpose を使用する必要があります。

🛠 使用例

defineExpose を使用した場合、子コンポーネントは下記のように書くことができます。

<script setup lang="ts">
import { ref } from "vue";

const count = ref<number>(0);

const increment = () => {
  count.value++;
};

defineExpose({
  increment,
});
</script>

<template>
  <div>
    <p>{{ count }}</p>
  </div>
</template>

count というリアクティブデータが定義されおり、increment という関数が実行されると数値が1ずつ増えるというシンプルなコンポーネントです。

公開したいプロパティを defineExpose 内に書くだけで外部に公開することができます。

increment という関数を外部に公開したい場合、下記のように書くことができます。

defineExpose({
  increment,
});

defineExpose を使用した子コンポーネントを呼び出す親コンポーネントは下記のように書くことができます。

<script setup lang="ts">
import { ref } from "vue";
import Count from "./Count.vue"

const count = ref<InstanceType<typeof Count> | null>(null);

const execIncrement = () => {
  count.value?.increment();
};
</script>

<template>
  <Count ref="count" />
  <button type="button" @click="execIncrement">increment</button>
</template>

テンプレート参照を使用することで、子コンポーネントで defineExpose を使用して定義した increment という関数にアクセスすることができます。

上記の例では、increment と書かれたボタンをクリックすると子コンポーネントの count というリアクティブデータが1つずつ増えることを確認することができます。

❓ Provide & Inject について

🪛 Provide

親または祖先にあたるコンポーネントから子または子孫にあたるコンポーネントに対してデータやメソッドを共有する機能です。

Provideは、defineExpose とは異なり、子また子孫にあたるコンポーネントから親または祖先にあたるコンポーネントに対してデータやメソッドを共有することはできません。

また、外部のスクリプトファイルに対してもデータやメソッドを共有することはできない点も defineExpose と異なる点です。

🪛 Inject

子または子孫にあたるコンポーネントが親または祖先にあたるコンポーネントから共有されたデータやメソッドを受け取る機能です。

🛠 使用例

Provide と Inject を使用する場合、下記のように書くことができます。

Provide を定義する親コンポーネントです。

先ほどの defineExpose で使用した例を再利用しています。

注意点としては、defineExpose とは異なり、親コンポーネントであることです。

<script setup lang="ts">
import { ref, provide } from "vue";
import Count from "./Count.vue"

const count = ref<number>(0);

const increment = () => {
  count.value++;
};

provide("increment", increment);
</script>

<template>
  <p>{{ count }}</p>
  <Count />
</template>

親コンポーネントから Provide で共有された increment という関数を Inject で受け取るために子コンポーネントは下記のように書きます。

<script setup lang="ts">
import { inject } from "vue";

const increment = inject<() => void>("increment")!;
</script>

<template>
  <div>
    <button type="button" @click="increment()">increment</button>
  </div>
</template>

子コンポーネントで定義した increment 書かれたボタンをクリックすると親コンポーネントの count というリアクティブデータが1つずつ増えることを確認することができます。

Provide と Injectは、子または子孫コンポーネントから親または祖先コンポーネントに対して情報を共有することはできません。

例えば、下記のコードはエラーになります。

子コンポーネントで定義した increment という関数を Provide で定義します。

<script setup lang="ts">
import { ref, provide } from "vue";

const count = ref(0);

const increment = () => {
  count.value++;
};

provide("increment", increment);
</script>

<template>
  <p>{{ count }}</p>
</template>

次に親コンポーネントで Inject を使用して increment という関数にアクセスしようとしてみます。

<script setup lang="ts">
import { inject } from "vue";
import Count from "./Count.vue"

const increment = inject<() => void>("increment")!;
</script>

<template>
  <div>
    <Count />
    <button type="button" @click="increment()">increment</button>
  </div>
</template>

上記のコードを実行すると次のエラーが表示されるはずです。

Vue warn: injection "increment" not found.

✅ まとめ

defineExpose を使用することで親または祖先コンポーネントだけでなく、外部のスクリプトファイルに対してもプロパティを公開することができます。

Provide は、親または祖先コンポーネントから子または子孫コンポーネントに対して情報を共有する機能であり、子または子孫コンポーネントから親または祖先コンポーネントに対して情報を共有することはできません。

Inject は、子または子孫にあたるコンポーネントが親または祖先にあたるコンポーネントから共有されたデータやメソッドを受け取るのみで外部にプロパティを公開したり、親または祖先コンポーネントに対して情報を共有することはできません。