404 Not Found

404 Not Found


nginx

Vue 3 コンポーネントの基礎:SFC(シングルファイルコンポーネント)+コンポーネントの登録

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

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

1. 学習内容


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

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

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

HTML
<!-- ❌ The "Flip" 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回再利用

VUE
<!-- components/ProductCard.vue - 1 Component Definitions -->
<template>
  <div class="product-card">
    <img :src="product.image">
    <h3>{{ product.name }}</h3>
    <p>${{ product.price }}</p>
    <button :disabled="product.stock === 0" @click="addToCart">
      {{ product.stock === 0 ? 'Out of Stock' : 'Add to Bag' }}
    </button>
  </div>
</template>

<script setup>
const props = defineProps({ product: Object })
const emit = defineEmits(['add-to-cart'])

function addToCart() {
  emit('add-to-cart', props.product.id)
}
</script>
VUE
<!-- HomeView.vue / ProductList.vue / SearchResults.vue -->
<template>
  <ProductCard v-for="product in products" :key="product.id" :product="product" @add-to-cart="handleAdd" />
</template>

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つのコンポーネントtemplatescriptstyleの各セクションを1つのファイルにまとめることで、管理が容易になります。

VUE
<!-- ProductCard.vue - SFC Single-File Components -->
<template>
  <!-- 1. template:HTML Template(Required) -->
  <div class="product-card">
    <h3>{{ product.name }}</h3>
    <p>${{ product.price }}</p>
  </div>
</template>

<script setup>
// 2. script:JS Logic(Required)
import { ref } from 'vue'

const props = defineProps({ product: Object })
const count = ref(0)
</script>

<style scoped>
/* 3. style:CSS Style(Optional) */
.product-card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 1rem;
}
</style>

(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の自動補完)
おすすめ度 ⭐⭐ プラグイン用 ⭐⭐⭐⭐⭐ ビジネス利用用

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

(1) 3 命名規則

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

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

VUE
<template>
  <!-- PascalCase(Recommendations,IDE Friendly) -->
  <ProductCard />
  <UserProfile />
  
  <!-- kebab-case(Also supported) -->
  <product-card />
  <user-profile />
  
  <!-- ✅ Either one works.,PascalCase Easier to read -->
</template>

(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) プロパティ:親から子へ渡される

VUE
<!-- Parent Component Parent.vue -->
<template>
  <ProductCard :product="myProduct" :show-stock="true" />
</template>

<!-- Child component ProductCard.vue -->
<template>
  <div>
    <h3>{{ product.name }}</h3>
    <p v-if="showStock">Stock: {{ product.stock }}</p>
  </div>
</template>

<script setup>
// Receive the data passed from the parent component props
const props = defineProps({
  product: { type: Object, required: true },
  showStock: { type: Boolean, default: false }
})
</script>

(2) 発散:子から親へ

VUE
<!-- Child component ProductCard.vue -->
<template>
  <button @click="handleAdd">Add to Cart</button>
</template>

<script setup>
const emit = defineEmits(['add-to-cart'])

function handleAdd() {
  emit('add-to-cart', { id: 1, name: 'iPhone' })
}
</script>
VUE
<!-- Parent Component Parent.vue -->
<template>
  <ProductCard @add-to-cart="handleAddToCart" />
</template>

<script setup>
function handleAddToCart(product) {
  console.log('Add to cart:', product)
}
</script>

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

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

8. 完全な例:5つのProductCardの再利用

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

VUE
<!-- src/components/ProductCard.vue -->
<template>
  <div :class="['product-card', { 'out-of-stock': product.stock === 0 }]">
    <img :src="product.image" :alt="product.name">
    <h3>{{ product.name }}</h3>
    <p class="price">${{ product.price }}</p>
    <span class="stock" v-if="product.stock < 10 && product.stock > 0">
      Only {{ product.stock }} left
    </span>
    <button 
      :disabled="product.stock === 0" 
      @click="handleAddToCart"
    >
      {{ product.stock === 0 ? 'Out of Stock' : 'Add to Cart' }}
    </button>
  </div>
</template>

<script setup>
const props = defineProps({
  product: { 
    type: Object, 
    required: true,
    validator: (val) => val.id && val.name && val.price
  }
})

const emit = defineEmits(['add-to-cart'])

function handleAddToCart() {
  if (props.product.stock > 0) {
    emit('add-to-cart', props.product.id)
  }
}
</script>

<style scoped>
.product-card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 1rem;
  transition: box-shadow 0.2s;
}
.product-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.out-of-stock {
  opacity: 0.6;
}
.price {
  color: #42b883;
  font-weight: bold;
}
button:disabled {
  background: #ccc;
  cursor: not-allowed;
}
</style>
▶ 試してみよう

(2) ▶ サンプル:. 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>

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

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>
▶ 試してみよう

(4) ▶ サンプル:. 小道具の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>
▶ 試してみよう

(5) ▶ サンプル:. コンポーネントの5つの設計原則

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

(6) ▶ サンプル:. よくある5つの間違い

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

❓ よくある質問

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

(2) 上級問題(難易度:⭐⭐)

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

(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%