Vue.js: コンポーネント基礎

最終更新:2026-10-20

Vueコンポーネントは再利用可能なVueインスタンスであり、各コンポーネントは独自のテンプレート、ロジック、スタイルをカプセル化しています。Vue 3の中核となるアーキテクチャは「コンポーネントツリー」です:ルートコンポーネント(App.vue)→ ページコンポーネント → ビジネスコンポーネント → 基本コンポーネント(Button/Input)。

コンポーネントの基礎を習得することは、大規模なVueアプリケーションを構築する上で極めて重要なステップです。このレッスンでは、コンポーネントの本質、登録方法、命名規則、そしてVueにおいてSFC(シングルファイルコンポーネント)が推奨される理由について理解を深めることができます。

1. 学習内容


2. コードの重複に悩まされる30ページのEコマース・バックエンド

(1) 課題:商品カード30枚、コピー&ペーストが30回必要

アリスのEコマース管理画面では、30枚の商品カードを複数のページにまたがって表示する必要がありました。彼女が最初に取ったアプローチは次のとおりでした:

HTML
<!-- ❌ The "Broken" Version: Copy Product Card 30 times -->
<!-- In HomeView.vue -->
<div class="product-card">
  <img src="iphone.jpg">
  <h3>iPhone 15 Pro</h3>
  <p>$999</p>
  <button>Add to Cart</button>
</div>
<div class="product-card">
  <img src="macbook.jpg">
  <h3>MacBook Pro</h3>
  <p>$2499</p>
  <button>Add to Cart</button>
</div>
<!-- ... And also 28 more ... -->

<!-- The same code appears again in ProductList.vue, SearchResults.vue, Cart.vue -->

プロダクトマネージャーのチャーリーが、5つの新しい要件を追加しました:

「アリス、『カートに追加』というボタンのテキストを『バッグに追加』に変更してほしい。あと、カードには在庫状況が表示されるようにして。それから、モバイルでもレスポンシブに対応するようにして。」

アリスは30以上のHTMLファイルを3回編集する必要がある=90回の編集。これは維持不可能です。

(2) Vueコンポーネントによる解決策:ProductCard.vueファイルを1つ作成し、30回再利用

1 ProductCard.vue → 30回再利用。ボタンのテキストを変更するには、1か所だけ変更すれば済みます。

(3) 収益

コンポーネント化後の状態:


3. コンポーネントとは何か?

(1) 定義

Vueにおけるコンポーネントとは、独自のロジックとスタイルを持つ再利用可能なUI単位のことです。各コンポーネントは、1つのVueインスタンスと、それ固有のデータ、メソッド、ライフサイクルで構成されています。

100%
graph TB
    A[App.vue<br/>Root Component] --> B[HomeView.vue<br/>Home]
    A --> C[AboutView.vue<br/>About]
    A --> D[DashboardView.vue<br/>Dashboard]
    
    B --> E[ProductCard.vue<br/>Product Card]
    B --> F[ProductList.vue<br/>Product List]
    B --> G[FilterBar.vue<br/>Filter Bar]
    
    E --> H[Button.vue<br/>Button]
    E --> I[Badge.vue<br/>Badge]
    
    F --> E
    G --> J[Select.vue<br/>Scroll down]
    G --> K[Input.vue<br/>Input]
    
    style A fill:#42b883,color:#fff
    style E fill:#42b883,color:#fff
    style H fill:#42b883,color:#fff

(2) 5つの主な特徴

機能 説明
再利用可能 1つのコンポーネントをNか所で使用可能
カプセル化 互いに干渉しないように、データ、メソッド、およびスタイルを分離すること
モジュール式 小さな構成要素を組み合わせて、より大きな構成要素を形成する
テスト可能 単一コンポーネントのユニットテスト
保守性 1か所を変更 = Nか所を変更

(3) コンポーネントと関数

寸法 機能 構成部品
再利用可能なコンポーネント コードロジック UI + ロジック
入力 引数 (args) プロパティ
出力 戻り値 出力
状態 ローカル変数 データ / 参照
副作用 なし ライフサイクルフック

4. シングルファイルコンポーネント(SFC)

(1) SFCとは何ですか?

SFC(シングルファイルコンポーネント)=1つの.vueファイル=1つのコンポーネント。 template、script、styleの各セクションを1つのファイルにまとめることで、管理が容易になります。

(2) SFC第3部の詳細な解説

セクション 機能 必須? 言語
<template> HTMLテンプレート ✅ HTML + Vueコマンド
<script setup> JS Logic ✅ JavaScript / TypeScript
<style scoped> CSSスタイル ❌ CSS / SCSS / Less

(3) SFC 対 HTML 文字列

JS
// ❌ No need SFC(String Templates,Vue 2 Style)
Vue.component('my-component', {
  template: '<div>{{ msg }}</div>',
  data() { return { msg: 'Hello' } }
})

// ✅ Use SFC (Recommended)
// MyComponent.vue
<template>
  <div>{{ msg }}</div>
</template>
<script setup>
const msg = 'Hello'
</script>

SFCのメリット:


5. コンポーネントの登録

(1) グローバル登録(大規模なプロジェクトには推奨されません)

JS
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import ProductCard from './components/ProductCard.vue'

const app = createApp(App)

// Global Registration:All components are available <ProductCard />
app.component('ProductCard', ProductCard)

app.mount('#app')

(2) 現地登録(推奨)

VUE
<!-- HomeView.vue -->
<script setup>
// 1. Import
import ProductCard from '@/components/ProductCard.vue'

// 2. Use in a template(No registration required,Automatically Available)
</script>

<template>
  <ProductCard :product="product" />
</template>

(3) 2つの登録方法の比較

次元 グローバル登録 ローカル登録
適用範囲 アプリケーション全体 現在のコンポーネント
コードの位置 main.js コンポーネント <script setup> 内
ツリーシェーキング ❌ 未対応 ✅ 対応
型ヒント 弱 強 (IDEの自動補完)
おすすめ度 ⭐⭐ プラグイン用 ⭐⭐⭐⭐⭐ ビジネス利用用

▶ サンプル:3つのページでProductCardを再利用する

VUE
<!-- src/views/HomeView.vue -->
<template>
  <h1>Featured Products</h1>
  <div class="grid">
    <ProductCard 
      v-for="product in featured" 
      :key="product.id" 
      :product="product"
      @add-to-cart="handleAdd"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ProductCard from '@/components/ProductCard.vue'
import { useCart } from '@/composables/useCart'

const featured = ref([
  { id: 1, name: 'iPhone 15 Pro', price: 999, stock: 50, image: 'iphone.jpg' },
  { id: 2, name: 'MacBook Pro', price: 2499, stock: 20, image: 'macbook.jpg' }
])

const { addToCart } = useCart()
function handleAdd(productId) {
  addToCart(productId)
}
</script>
▶ 試してみよう
VUE
<!-- src/views/ProductListView.vue -->
<template>
  <ProductCard 
    v-for="product in products" 
    :key="product.id" 
    :product="product"
    @add-to-cart="handleAdd"
  />
</template>

<script setup>
import ProductCard from '@/components/ProductCard.vue'
// ... Exactly the same citation,Reusable Components
</script>

出力:

TEXT
Featured Products の下に iPhone 15 Pro と MacBook Pro のカードが 2 枚並ぶ
Add to Cart を押すと add-to-cart が発火し useCart の addToCart(productId) が呼ばれる

▶ サンプル:グローバル登録とローカル登録の比較

JS
// ❌ main.js Global Registration(Not recommended)
import ProductCard from './components/ProductCard.vue'
app.component('ProductCard', ProductCard)  // The entire app is available

// ✅ HomeView.vue Local Registration(Recommendations)
<script setup>
import ProductCard from '@/components/ProductCard.vue'
// Only at HomeView.vue Available in China
</script>
▶ 試してみよう

出力:

TEXT
グローバル登録:どのコンポーネントからでも ProductCard が解決できる(常にバンドルされる)
ローカル登録:HomeView.vue の中だけで ProductCard が解決できる

6. コンポーネントの命名規則

(1) 3 命名規則

スタイル 例 使い方 推奨事項
PascalCase ProductCard.vue JSのインポート / テンプレート内 ⭐⭐⭐⭐⭐
ケバブ式大文字小文字 product-card.vue ファイル名 / ケバブ式大文字小文字のテンプレート ⭐⭐⭐
camelCase productCard.vue 推奨されません ⭐

(2) テンプレートでの使用

(3) フォルダ構成

TEXT
src/
├── components/          # Public Components(For use across pages)
│   ├── ProductCard.vue
│   ├── Button.vue
│   └── Header.vue
├── views/               # Page Components(For routing)
│   ├── HomeView.vue
│   ├── ProductListView.vue
│   └── DashboardView.vue
├── composables/         # Composite Functions
│   ├── useAuth.ts
│   └── useFetch.ts
├── router/              # Router Configuration
└── App.vue              # Root Component

7. props と emit の基礎

(1) プロパティ:親から子へ渡される

(2) 発散:子から親へ

(3) props と emit のクイックリファレンス

カテゴリ API 目的
親 → 子 defineProps() データ転送
子 → 親 defineEmits() イベント通知

▶ サンプル:小道具の5つの主な種類

VUE
<script setup>
defineProps({
  // Basic Types
  name: String,
  age: Number,
  active: Boolean,
  
  // Complex Types
  user: Object,
  items: Array,
  
  // Required + Default value
  title: { type: String, required: true },
  pageSize: { type: Number, default: 20 },
  
  // Validator
  email: { 
    type: String, 
    validator: (val) => val.includes('@') 
  },
  
  // Custom Types
  status: { 
    type: String, 
    validator: (val) => ['active', 'inactive'].includes(val) 
  }
})
</script>
▶ 試してみよう

出力:

TEXT
required の title を渡さないと Vue が警告を出す
pageSize 未指定なら 20 が既定値になる
email に @ が無い、または status が active・inactive 以外だとバリデータが警告する

(1) 📊 . コンポーネントの5つの設計原則

原則 説明
単一責任の原則 1つのコンポーネントは1つのことだけを行う
再利用可能 抽象的な汎用UI(ボタン/入力フィールド/モーダル)
設定可能 props は動作を制御し、slots はコンテンツを制御する
モジュール式 小さな構成要素を組み合わせて、より大きな構成要素を形成する
テストが容易 独立したコンポーネントにより、ユニットテストが容易

(2) 📊 . よくある5つの間違い

エラー 症状 解決策
コンポーネント一覧ファイル IDEエラー PascalCaseを使用してください: ProductCard.vue
グローバル登録が多すぎる バンドルサイズが大きい ローカル登録はツリーシェーキングに対応
コンポーネントが大きすぎる メンテナンスが困難 より小さなコンポーネントに分割する
props を直接変更 Vue の警告 props は読み取り専用です。代わりに emit を使用してください
コンポーネントのインポートを忘れた テンプレートに「不明なコンポーネント」と表示される <script setup> のインポート設定を確認してください


8. 総合演習:ProductCard コンポーネントの完成

▶ サンプル:ProductCard.vue シングルファイルコンポーネント

▶ 試してみよう

出力:

TEXT
stock 50 → $999 と Add to Cart(有効)
stock 5 → Only 5 left バッジが追加表示される
stock 0 → Out of Stock 表示+ボタン無効、カードは半透明になる


❓ よくある質問

Q グローバル登録はどのような場合に使用すべきですか?
A 以下の2つの状況に限られます:(1) プラグインコンポーネント(Element Plusの<el-button>など);(2) アプリケーション全体で使用される基本コンポーネント(Button/Icon)。ビジネスコンポーネントは、ツリーシェイクが有効になるよう、常にローカルに登録する必要があります。
Q SFCには3つのパーツすべてが必要ですか?
A <template>と<script>は必須ですが、<style>は任意です。コンポーネントがJavaScriptのみで構成されている場合(例えば、データカプセル化のみを目的としている場合など)、代わりにテンプレートを省略することも可能です(render関数を使用します)。
Q コンポーネントファイルの適切なサイズとは?
A 200行以内に収めることをお勧めします。300行を超える場合は、分割することを検討してください。コンポーネントがシンプルであればあるほど、メンテナンスが容易になります。
Q SFCでは.vueという拡張子が使われますが、他のツールはこれをどのように認識するのでしょうか?
A Vite、Webpack、Vue CLIはいずれも、デフォルトで.vueという拡張子を認識します。VS Codeの場合は、「Vue - Official」拡張機能をインストールしてください。
Q 単一のコンポーネントをテストするにはどうすればよいですか?
A Vitest と Vue Test Utils (@vue/test-utils) を使用してください:mount(ProductCard, { props: { product: mockProduct } })。このチュートリアルのフェーズ 4.6 で詳しく解説しています。
Q コンポーネントライブラリ(Element Plus)はどのように使えばよいですか?
A npm 経由でインストール → main.js でグローバルに登録 → テンプレート内で <el-button> を直接使用します。詳細については、Phase の第 4.7 章を参照してください。
Q SFCとReactのJSXの違いは何ですか?
A SFCはHTMLに似たテンプレート(Web標準に近い)を使用するのに対し、ReactはJSX(JavaScriptでHTMLを記述する方法)を使用します。SFCは習得が容易ですが、JSXは柔軟性が高い反面、ビルド工程が必要となります。Vue 3もJSX(@vitejs/plugin-vue-jsx)をサポートしていますが、デフォルトではSFCの使用が推奨されています。

📖 まとめ


📝 練習問題

  1. 基礎問題(難易度:⭐)

    Button コンポーネントを作成する src/components/BaseButton.vue:

    • props: text (文字列), variant (文字列, 'primary'/'success'/'danger')
    • 3つのバリエーションは3つのクラスに対応しています
    • click イベントを発行する
    • HomeView.vue で 3 種類のバリエーションを使用する
  2. 上級問題(難易度:⭐⭐)

    商品リストとカードシステムの作成:

    • ProductCard.vue: 単一商品カード(props: product、emit: add-to-cart)
    • ProductList.vue: 商品リストのコンテナ(v-for + フィルタリング)
    • App.vue:ルートコンポーネント。データを渡し、「カートに追加」イベントを処理する
    • ネストされた構造で使用される3つのファイルと3つのコンポーネント
  3. チャレンジ問題(難易度:⭐⭐⭐)

    完全な「ユーザーカード」コンポーネントシステムを実装する:

    1. Avatar.vue: アバターコンポーネント(URLモードとイニシャルモードの両方に対応)
    2. UserCard.vue:ユーザーカード(アバター付き)
    3. UserList.vue: ユーザー一覧(UserCard を使用)
    4. App.vue:ルートコンポーネント(データ+インタラクション)
    5. 5つのプロパティ(アバター/名前/メールアドレス/役割/アクティブ)
    6. 3 イベントの発生(編集/削除/選択)
    7. スコープによるスタイルの分離
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%