この記事の目次
Vue 3 コンポーネントの基本とは
コンポーネントは、画面を作る「部品」です。ボタン、カード、リストの1行のように、見た目と動きをひとまとまりにして、何度でも使い回せるようにしたものです。公式ドキュメントでは、コンポーネントによって UI を独立した再利用可能な部品に分割し、それぞれを切り離して考えられるようになると説明しています。
この記事では、.vue ファイル(単一ファイルコンポーネント)で部品を作る方法、部品を使えるようにする「登録」、親子のやりとり(props と emit)、中身を差し込む slot までを、実際にビルドして動かした例で説明します。
基本(作る・登録する・使う)
| 手順 | 書き方 | ポイント |
|---|---|---|
| 作る | TodoItem.vue などのファイルを作る |
ファイル名は PascalCase(単語の頭を大文字)が一般的 |
| 登録する | <script setup> で import TodoItem from '…' |
import するだけで、そのファイルのテンプレートで使える(ローカル登録) |
| 使う | <TodoItem /> |
.vue の中では自己終了タグも使える |
登録には2種類あります。
| 登録 | 書き方 | 特徴 |
|---|---|---|
| ローカル登録 | 使うファイルで import する |
どこで使っているかが分かりやすい。基本はこれ |
| グローバル登録 | app.component('名前', 部品) |
アプリ全体で使える。使っていなくてもビルドから外れにくい |
公式ドキュメントは、グローバル登録には、使われていない部品がビルドから取り除かれにくい、どこで使われているか分かりにくい、といった欠点があると説明しています。
サンプルコード(実行結果つき)
src/components/TodoItem.vue(子の部品)
<script setup>
defineProps({
text: { type: String, required: true }
})
const emit = defineEmits(['remove'])
</script>
<template>
<li>
{{ text }}
<button type="button" @click="emit('remove')">削除</button>
</li>
</template>
src/components/BaseCard.vue(中身を差し込む枠)
<template>
<section class="card">
<slot>(中身がありません)</slot>
</section>
</template>
<style scoped>
.card {
border: 1px solid #cccccc;
padding: 8px 16px;
}
</style>
親の部品
<script setup>
import { ref } from 'vue'
import BaseCard from '@/components/BaseCard.vue'
import TodoItem from '@/components/TodoItem.vue'
const todos = ref([
{ id: 1, text: '牛乳を買う' },
{ id: 2, text: '本を返す' }
])
function removeTodo(id) {
todos.value = todos.value.filter((t) => t.id !== id)
}
</script>
<template>
<BaseCard>
<h2>やること({{ todos.length }}件)</h2>
<ul>
<TodoItem v-for="t in todos" :key="t.id" :text="t.text" @remove="removeTodo(t.id)" />
</ul>
</BaseCard>
<BaseCard />
</template>
動作確認(検証環境と結果)
- Vue 3.5.43、Vite 8.3.3(
npm create vue@latestで作成)、Microsoft Edge、2026年10月。npm run buildのあとnpm run previewで確認しました。 - 最初は「やること(2件)」「牛乳を買う 削除」「本を返す 削除」と表示されました。
- 「牛乳を買う」の削除を押すと「やること(1件)」になり、「本を返す」だけが残りました。
- 中身を書かずに置いた
<BaseCard />には、slot の初期値の「(中身がありません)」が表示されました。
Vue 3 コンポーネントの基本の注意点
- props は親から子への一方通行です。 子は受け取った props を書き換えません。変えたいときは、emit で親に知らせ、データを持つ親が変えます。
- slot の初期値:
<slot>の中に書いた内容は、親が何も渡さなかったときだけ表示されます。 - HTML に直接書くテンプレート(CDN 版)では書き方が違います。 部品名を
todo-itemのような小文字のハイフン区切りで書き、<todo-item />のような自己終了タグは使えません。
Vue 3 コンポーネントの基本でよくあるミス
- import を書き忘れて、部品が表示されない(コンソールに警告が出ます)。
- v-for で部品を繰り返すときに
:keyを付け忘れる。 - 1つの部品に機能を詰め込みすぎ、逆に分けすぎて、どこに何があるか分からなくなる。
Vue 3 コンポーネントの基本のチェックリスト
- [ ] 部品を
.vueファイルに分けた - [ ] 使うファイルで import してローカル登録した
- [ ] defineProps で受け取る値を決めた
- [ ] 子から親へは defineEmits で知らせた
- [ ] 枠だけの部品には slot を使った
Vue 3 コンポーネントの基本のFAQ(よくある質問)
Q. どのくらいの大きさで部品に分ければいいですか?
A. 2回以上使うもの、またはファイルが長くなって読みにくくなったものを分ける、という目安が分かりやすいです。
Q. defineProps は import しなくていいのですか?
A. defineProps と defineEmits は <script setup> の中で使えるコンパイラーマクロで、import は不要です。
Q. slot は複数置けますか?
A. 名前付きスロット(<slot name="header">)を使うと、場所ごとに別の中身を差し込めます。
筆者の見解(Vue 3 コンポーネントの基本)
コンポーネントは Vue の中心ですが、最初から細かく分けようとすると、かえって迷いやすいと感じます。私は、まず1つのファイルで画面を作り、同じ形が2回出てきた所から部品にする進め方を勧めます。そのうえで「データを持つのは親、子は表示と知らせるだけ」という役割分担を守れば、部品が増えても見通しが保てると考えます。
Vue 3 コンポーネントの基本の関連項目
- Vue 3 props と emit の使い方|親子コンポーネントのデータ受け渡し(近日公開)
- Vue 3 script setupとは|書き方・defineProps・defineEmits(近日公開)
- Vue 3 テンプレート構文の基本|{{ }}・v-bind・v-onの書き方
- Vue 3 v-forとkeyの使い方|indexをkeyにしてはいけない理由(近日公開)
- npm create vue@latestの使い方|Vueプロジェクト作成
