Skip to content
sumnail

コンポーネント間の連携

created at : 2024/05/19

Vue3

Props

親コンポーネントから子コンポーネントへデータを渡す方法を解説します。
親から子へデータを渡すときはPropsを使用します。

親からバインディングによって、子のコンポーネントにデータを渡します。
基本的に親から子へ渡した変数は、子コンポーネント内ではreadOnlyになります。

子コンポーネントは、それぞれ独立しており一方のリアクティブが変更されても他のリアクティブは変更されないことが確認できます。

DANGER

子コンポーネントのリアクティブ変数を直接 props に変えると、コンソールにエラーが表示されます。

WARNING

親コンポーネントの変数を変更できないので、タスクチェックしても完了タスクの合計は増えません。

完了タスク : 0 / 2

  • Buy Apple.

  • Go Gym.

vue
<template>
  <div>
    <p>完了タスク : {{ countCompleted }} / {{ totalTask }}</p>
  </div>
  <ul>
    <li
      v-for="(item, index) in items"
      :key="index"
    >
      <PropsOneItem
        :id="item.id"
        :name="item.name"
        :completed="item.completed"
      />
    </li>
  </ul>
  <div>

  </div>
</template>
vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import PropsOneItem from './PropsOneItem.vue';

interface Items {
    id: number;
    name: string;
    completed: boolean;
}

const items = ref<Items[]>([
    { id: 1, name: 'Buy Apple.', completed: false},
    { id: 2, name: 'Go Gym.', completed: false},
]);

const countCompleted = computed(() => {
  return items.value.reduce((acc, value) => acc + Number(value.completed), 0);
})

const totalTask = computed(() => {
  return items.value.length;
})
</script>
vue
<template>
  <div class="item">
    <input
      type="checkbox"
      :value="itemCompleted"
    >
    <p class="task-name">
      {{ name }}
    </p>
  </div>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue';

interface Items {
  id: number;
  name: string;
  completed: boolean;
}

const props = defineProps<Items>();

const itemCompleted = ref(props.completed);  
</script>

Emits

上のタスク管理は、タスク全体の進捗を把握するには不十分です。
タスクをチェックしたら、タスク進捗にも反映したいですね。

子コンポーネントでの変更を親コンポーネントに反映する(教える)仕組みが必要になります。
ここでEmitsを使用します。

親のイベントハンドラーを、子コンポーネントの emit で受け取ります。
@change-check-box="item.completed = !item.completed"
子はイベントハンドラーonCheckBoxの中で、emit("changeCheckBox")に着火しています。

INFO

タスクチェックすると、完了タスクの合計に反映されます。

完了タスク : 0 / 2

  • Buy Apple.

  • Go Gym.

vue
<template>
  <div>
    <p>完了タスク : {{ countCompleted }} / {{ totalTask }}</p>
  </div>
  <ul>
    <li
      v-for="(item, index) in items"
      :key="index"
    >
      <EmitsOneItem
        :id="item.id"
        :name="item.name"
        :completed="item.completed"
        @change-check-box="item.completed = !item.completed"
      />
    </li>
  </ul>
  <div>

  </div>
</template>
vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import EmitsOneItem from './EmitsOneItem.vue';

interface Items {
    id: number;
    name: string;
    completed: boolean;
}

const items = ref<Items[]>([
    { id: 1, name: 'Buy Apple.', completed: false},
    { id: 2, name: 'Go Gym.', completed: false},
]);

const countCompleted = computed(() => {
  return items.value.reduce((acc, value) => acc + Number(value.completed), 0);
})

const totalTask = computed(() => {
  return items.value.length;
})
</script>
vue
<template>
  <div class="item">
    <input
      type="checkbox"
      :value="itemCompleted"
      @change="onCheckBox"
    >
    <p class="task-name">
      {{ name }}
    </p>
  </div>
</template>
vue
<script setup lang="ts">
import { ref } from 'vue';

interface Items {
  id: number;
  name: string;
  completed: boolean;
}

interface Emits {
  (event: "changeCheckBox"): void;
}

const props = defineProps<Items>();
const emit = defineEmits<Emits>();

const itemCompleted = ref(props.completed);

const onCheckBox = () => {
  emit("changeCheckBox");
}
  
</script>

v-model

Props と Emits の基本を抑えられたと思います。

Props と Emits で親子コンポーネント間の変更をリアクティブにしましたが、 v-modelを使用することで、より簡潔にリアクティブにすることが出来ます。

v-model を使ったコンポーネント間の内部の動きを確認することが出来ます。

INFO

Vue 3.4 以降は、defineModel()を使うことが推奨されています。

こちらも併せてご確認ください!

完了タスク : 0 / 2

  • Buy Apple.

  • Go Gym.

vue
<template>
  <div>
    <p>完了タスク : {{ countCompleted }} / {{ totalTask }}</p>
  </div>
  <ul>
    <li
      v-for="(item, index) in items"
      :key="index"
    >
      <VModelOneItem
        :id="item.id"
        :name="item.name"
        v-model:completed="item.completed"
      />
    </li>
  </ul>
  <div>

  </div>
</template>
vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import VModelOneItem from './VModelOneItem.vue';

interface Items {
    id: number;
    name: string;
    completed: boolean;
}

const items = ref<Items[]>([
    { id: 1, name: 'Buy Apple.', completed: false},
    { id: 2, name: 'Go Gym.', completed: false},
]);

const countCompleted = computed(() => {
  return items.value.reduce((acc, value) => acc + Number(value.completed), 0);
})

const totalTask = computed(() => {
  return items.value.length;
})
</script>
vue
<template>
  <div class="item">
    <input
      type="checkbox"
      :value="completed"
      @change="onCheckBox"
    >
    <p class="task-name">
      {{ name }}
    </p>
  </div>
</template>
vue
<script setup lang="ts">

interface Items {
  id: number;
  name: string;
  completed: boolean;
}

interface Emits {
  (event: "update:completed", completed: boolean): void;
}

const props = defineProps<Items>();
const emit = defineEmits<Emits>();

const onCheckBox = (event) => {
  const element = event.target as HTMLInputElement;
  emit("update:completed", event.target.checked);
}
  
</script>