Vue.js

コンポーネントとProps

UIを部品へ分け、親子のデータの流れを設計します。

Vue.js入門の講座一覧

01

Single-File Component

Vueの.vueファイルは、テンプレート、ロジック、スタイルを一つの部品にまとめます。<script setup>で宣言した変数や関数は、そのままテンプレートから使えます。

Badge.vueVUE
<script setup>
defineProps({ label: { type: String, required: true } })
</script>

<template>
  <span class="badge">{{ label }}</span>
</template>

<style scoped>
.badge { padding: .25rem .5rem; border-radius: 8px; background: #e8f4fa; }
</style>

scopedを付けると、そのコンポーネントの要素を中心にスタイルが適用されます。コンポーネント名は大文字始まりにし、役割が分かる名前を付けます。

02

Propsで親から値を渡す

Propsは親から子への入力です。子は読み取り専用として扱い、表示や計算に利用します。必須か、文字列か、数値かを定義すると間違いに気付きやすくなります。

ProductCard.vueVUE
<script setup>
const props = defineProps({
  product: { type: Object, required: true }
})
</script>

<template>
  <article>
    <h2>{{ props.product.name }}</h2>
    <p>{{ props.product.price }}円</p>
  </article>
</template>
親のテンプレートVUE
<ProductCard :product="{ name: 'ノート', price: 300 }" />
Propsを直接変更しない
子がprops.product.name = ...のように親のデータを変更すると、データの所有者が不明になります。変更はイベントで親へ依頼します。
03

emitsでイベントを返す

子が「削除してほしい」「選択された」と伝えるには、defineEmitsでイベントを定義します。親は@イベント名で受け取ります。

子コンポーネントVUE
<script setup>
defineProps({ id: { type: Number, required: true } })
const emit = defineEmits(['remove'])
</script>

<template>
  <button @click="emit('remove', id)">削除</button>
</template>
親コンポーネントVUE
<TodoItem :id="todo.id" @remove="removeTodo" />

<script setup>
function removeTodo(id) {
  todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>

この構造では、配列を持つ親が削除を担当します。子はボタンの役割、親はデータ変更の役割を持つため、それぞれを独立して確認できます。

04

slotとコンポーネント間の設計

slotは、子コンポーネントの枠へ親から内容を差し込む仕組みです。カードの外枠だけを共通化し、タイトルや本文を呼び出し側で変えたい場合に使います。

Panel.vueと利用側VUE
<!-- Panel.vue -->
<section class="panel">
  <slot />
</section>

<!-- 親 -->
<Panel>
  <h2>お知らせ</h2>
  <p>更新が完了しました。</p>
</Panel>

同じコンポーネントへ双方向の入力を渡したい場合は、modelValueというPropsとupdate:modelValueイベント、またはdefineModelを使います。まずはPropsとemitsの一方向の流れを理解してから進むと混乱しません。