13
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vueのコンポーネント間のデータ共有方法

Last updated at Posted at 2025-01-29

はじめに

私は駆け出しのエンジニアで、最近Vueに初めて触れました。
コンポーネント間のデータ共有方法は多種多様であり、各方法の用途について混乱してきたため、比較しまとめようと思いました。
その矢先、下記の非常に有り難い記事に出会ってしまい、一瞬自分がまとめる意義を見失いましたが、

今回は、

  • 上記記事で紹介されているデータ共有方法のうち、私が現時点で使用したことのあるものに限定し
  • 各方法について再調査後、私なりの分類・言語化で再構成し
  • サンプルコードを付けることで

自分なりのチートシートのようなものを作成することとしました。

結論:いつ、どの方法を使うべきか

  • データ共有
    • コンポーネントローカル
      • 親→子の一方向:props
      • 親→子孫の一方向(※):provide/inject
      • 親子間の双方向:defineModel
    • グローバル:状態管理ライブラリ
  • DOM要素渡し(親→子):slot
  • イベント渡し(子→親):emit

※propsと比較して、兄弟間のデータ共有を行いたい場合に有用、親を追跡しにくいことが難点

データ共有

コンポーネントローカル

props

  • 親から子への一方向のデータ渡し
  • 子で受け入れるデータを定義し、親が呼び出す際にデータを渡す

用途

  • 親から子への一方向のデータ渡しをしたい場合
ParentComponent.vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';

const message = 'Hello from parent';
</script>

<template>
  <ChildComponent :message="message" />
</template>
ChildComponent.vue
<script setup lang="ts">
import { defineProps } from 'vue';

const props = defineProps<{ 
  message: string;
}>();
</script>

<template>
  <div>{{ props.message }}</div>
</template>

provide/inject

  • 親から子孫への一方向のデータ渡し
  • 親でprovideによりデータを提供し、子孫でinjectにより受け取る
    • 中間のコンポーネントは経由する必要がない

用途

  • 親からデータを渡す対象コンポーネントの、階層が深い場合
    • ただし、深い階層でも使用できる分、provideした親の追跡が不明確になる
    • 下記の状態管理ライブラリの使用も検討すべき
  • 親からのデータを兄弟間でも共有する場合、子で個別の定義が必要なpropsよりも簡潔
ParentComponent.vue
<script setup lang="ts">
import { provide } from 'vue';

const message = 'Shared message';
provide('messageKey', message);
</script>

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

const message = inject('messageKey');
</script>

<template>
  <div>{{ message }}</div>
</template>

defineModel

  • 親子間でのデータの双方向共有
  • 子でpropsとemitを同時に定義し、親でv-modelによってデータをバインドする

用途

  • 親子間で、データを双方向にバインディングしたい場合
ParentComponent.vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';

const sharedValue = ref('');
</script>

<template>
  <ChildComponent v-model="sharedValue" />
  <p>{{ sharedValue }}</p>
</template>
ChildComponent.vue
<script setup lang="ts">
import { defineModel } from 'vue';

const modelValue = defineModel<string>('modelValue');
</script>

<template>
  <input v-model="modelValue">
</template>

グローバル

状態管理ライブラリ(Vuex, Pinia)

  • 親子関係によらない、グローバルなデータ管理
  • アプリケーション全体でデータを一元管理し、複数のコンポーネント間で共有できるようにする

用途

  • アプリケーションが大規模で、propsなどでは煩雑になる場合
    • 大規模でも、コンポーネントローカルな共有にはpropsでよい
counterStore.ts
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', () => {
    const count = ref(0);
  
    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment
    };
});
Component.vue
<script setup lang="ts">
import { useCounterStore } from './store';

const counterStore = useCounterStore();
const { count, increment } = counterStore;
</script>

<template>
  <p>{{ count }}</p>
  <button @click="increment">Increment</button>
</template>

DOM要素渡し(親→子)

slot

  • 親から子にDOM要素を渡す

用途

  • DOM要素を渡したい場合
ParentComponent.vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
</script>

<template>
  <ChildComponent>
    <p>Content</p>
  </ChildComponent>
</template>
ChildComponent.vue
<script setup lang="ts">
</script>

<template>
  <div>
    <slot></slot>
  </div>
</template>

イベント渡し(子→親)

emit

  • 子でイベントを発火させ、親で受け取る
  • 副次的な効果として、子のデータを親へ渡す
    • emitメソッドのオプショナルな引数でデータを渡せる
    • あくまで、子のイベント発火に伴って行う、親の処理に必要な材料を渡す役割

一方向のデータフローに違反しない理由

  • propsは、親が子にデータを渡すことによって、子の状態を変化させる
  • 一方、emitのイベント渡しは、イベントが発火したことの単なる通知に過ぎないし、データ渡しは付属情報の送信である
  • それらを利用するかどうかは親に委ねられており、親の状態が強制的に変更されるわけではないので、一方向のデータフローは守られている

用途

  • 子のイベント発火を親に渡したい場合
  • イベント発火に付属するデータを子から親に渡したい場合
ParentComponent.vue
<script setup lang="ts">
function handleUpdate(data: string) {
  console.log(data);
}
</script>

<template>
  <ChildComponent @update="handleUpdate" />
</template>
ChildComponent.vue
<script setup lang="ts">
import { defineEmits } from 'vue';

const emit = defineEmits(['update']);

function handleClick() {
  emit('update', 'Updated from child');
}
</script>

<template>
  <button @click="handleClick">Update Parent</button>
</template>

おわりに

今回調べきれていないデータ共有方法として、expose, scoped slot, グローバルrefもあるようです。
逐次それらについても習得していきたいです。

参考文献

13
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?