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

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

https://qiita.com/k_orita/items/25942d00f0ce9b3ffda2

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

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

# 結論：いつ、どの方法を使うべきか
- データ共有
    - コンポーネントローカル
        - 親→子の一方向：props
        - 親→子孫の一方向（※）：provide/inject
        - 親子間の双方向：defineModel
    - グローバル：状態管理ライブラリ
- DOM要素渡し（親→子）：slot
- イベント渡し（子→親）：emit

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

# データ共有
## コンポーネントローカル
### props

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

**用途**
- 親から子への一方向のデータ渡しをしたい場合

```vue:ParentComponent.vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';

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

<template>
  <ChildComponent :message="message" />
</template>
```

```vue: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よりも簡潔

```vue:ParentComponent.vue
<script setup lang="ts">
import { provide } from 'vue';

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

<template>
  <ChildComponent />
</template>
```

```vue: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によってデータをバインドする

**用途**
- 親子間で、データを双方向にバインディングしたい場合

```vue: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>
```

```vue: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でよい

```typescript:counterStore.ts
import { defineStore } from 'pinia';

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

    return {
      count,
      increment
    };
});
```

```vue: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要素を渡したい場合

```vue:ParentComponent.vue
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue';
</script>

<template>
  <ChildComponent>
    <p>Content</p>
  </ChildComponent>
</template>
```

```vue:ChildComponent.vue
<script setup lang="ts">
</script>

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

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

**用途**
- 子のイベント発火を親に渡したい場合
- イベント発火に付属するデータを子から親に渡したい場合

```vue:ParentComponent.vue
<script setup lang="ts">
function handleUpdate(data: string) {
  console.log(data);
}
</script>

<template>
  <ChildComponent @update="handleUpdate" />
</template>
```

```vue: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もあるようです。
逐次それらについても習得していきたいです。

# 参考文献

https://ja.vuejs.org/guide/introduction.html

https://qiita.com/k_orita/items/25942d00f0ce9b3ffda2

https://qiita.com/pham_thanh_thuong/items/46eae855b7600d1589b5

https://zenn.dev/sindicum/articles/f9e737fba22281#4.provide%2Finject