Vue.js: Component Basics
Last updated: 2026-08-26
Vue components are reusable Vue instances—each component encapsulates its own template, logic, and styles. The core architecture of Vue 3 is the "component tree": root component (App.vue) → page components → business components → basic components (Button/Input).
Mastering the basics of components is a crucial step in building large-scale Vue applications. This lesson will help you understand the nature of components, how to register them, naming conventions, and why SFCs (single-file components) are the recommended approach in Vue.
1. What You'll Learn
- What is a component? The relationship between components and Vue instances
- The three parts of the Single-File Component (SFC): template, script, and style
- The Difference Between Global and Local Registration and How to Choose Between Them
- Component Naming Conventions (PascalCase / kebab-case)
- Component Reuse (Avoiding Duplicate Code)
- Basic concepts of
propsandemit(covered in detail in the next two lessons) - 5 Component Design Principles
2. A 30-Page E-Commerce Backend Plagued by Code Duplication
(1) Pain Point: 30 product cards, 30 instances of copy and paste
Alice's e-commerce admin needed to display 30 product cards across multiple pages. Her initial approach:
<!-- ❌ 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 -->
The product manager Charlie adds 5 new requirements:
"Alice, I need to change the button text from 'Add to Cart' to 'Add to Bag'. Also, the card should show stock status. And it needs to be responsive on mobile."
Alice has to edit 30+ HTML files 3 times = 90 edits. This is impossible to maintain.
(2) Vue Component Solution: 1 ProductCard.vue file, reused 30 times
<!-- 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 iftup>
const props = defineProps({ product: Object })
const emit = defineEmits(['add-to-cart'])
function addToCart() {
emit('add-to-cart', props.product.id)
}
</script>
<!-- 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 → reused 30 times. To change the button text, you only need to make the change in one place.
(3) Revenue
After componentization:
- Code Volume: 30 × 15 lines × 3 pages = 1350 lines → 1 component + 30 lines × 3 = 90 lines (-93%)
- 1 change: 30 product cards are automatically updated
- Add a new feature: Add it to the component, and it will automatically appear on all pages
- Testability: ProductCard.vue can be tested independently
3. What is a component?
(1) Definition
A component in Vue is a reusable UI unit with its own logic and styling. Each component consists of one Vue instance plus its own data, methods, and lifecycle.
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 Key Features
| Feature | Description |
|---|---|
| Reusable | 1 component can be used in N places |
| Encapsulation | Separate data, methods, and style that do not interfere with one another |
| Modular | Smaller components combined to form larger components |
| Testable | Unit testing a single component |
| Maintainable | Change 1 place = Change N places |
(3) Components vs. Functions
| Dimension | Function | Component |
|---|---|---|
| Reusable Components | Code Logic | UI + Logic |
| Input | Arguments (args) | props |
| Output | return | emit |
| State | Local Variables | data / refs |
| Side Effects | None | Lifecycle Hooks |
4. Single-File Component (SFC)
(1) What is SFC?
SFC (Single File Component) = 1 .vue file = 1 component. Combining the template, script, and style sections into a single file makes it easier to manage.
<!-- 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 iftup>
// 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) Detailed Explanation of SFC Part 3
| Section | Function | Required? | Language |
|---|---|---|---|
<template> |
HTML template | ✅ | HTML + Vue command |
<script setup> |
JS Logic | ✅ | JavaScript / TypeScript |
<style scoped> |
CSS Styles | ❌ | CSS / SCSS / Less |
(3) SFC vs HTML string
// ❌ No need SFC(String Templates,Vue 2 Style)
Vue.component('my-component', {
template: '<div>{{ msg }}</div>',
data() { return { msg: 'Hello' } }
})
// ✅ Uif SFC (Recommended)
// MyComponent.vue
<template>
<div>{{ msg }}</div>
</template>
<script iftup>
const msg = 'Hello'
</script>
SFC Advantages:
- Editor support (syntax highlighting, navigation, auto-completion)
- Toolchain support (Vite hot reloading, TypeScript type checking)
- Separation of Concerns (1 block each for HTML, JS, and CSS)
5. Component Registration
(1) Global Registration (Not Recommended for Large Projects)
// 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) Local Registration (Recommended)
<!-- HomeView.vue -->
<script iftup>
// 1. Import
import ProductCard from '@/components/ProductCard.vue'
// 2. Uif in a template(No registration required,Automatically Available)
</script>
<template>
<ProductCard :product="product" />
</template>
(3) Comparison of the Two Registration Methods
| Dimension | Global Registration | Local Registration |
|---|---|---|
| Scope | Entire application | Current component |
| Code Location | main.js |
Within the component <script setup> |
| Tree-shaking | ❌ Not supported | ✅ Supported |
| Type hints | Weak | Strong (IDE auto-completion) |
| Recommendation | ⭐⭐ For plugins | ⭐⭐⭐⭐⭐ For business use |
6. Component Naming Conventions
(1) 3 Naming Conventions
| Style | Example | Usage | Recommendation |
|---|---|---|---|
| PascalCase | ProductCard.vue |
JS import / In Templates | ⭐⭐⭐⭐⭐ |
| kebab-case | product-card.vue |
Filenames / kebab-case Templates | ⭐⭐⭐ |
| camelCase | productCard.vue |
Not recommended | ⭐ |
(2) Using in Templates
<template>
<!-- PascalCaif(Recommendations,IDE Friendly) -->
<ProductCard />
<UifrProfile />
<!-- kebab-caif(Also supported) -->
<product-card />
<uifr-profile />
<!-- ✅ Either one works.,PascalCaif Easier to read -->
</template>
(3) Folder Structure
src/
├-- components/ # Public Components(For uif across pages)
│ ├-- ProductCard.vue
│ ├-- Button.vue
│ └-- Header.vue
├-- views/ # Page Components(For routing)
│ ├-- HomeView.vue
│ ├-- ProductListView.vue
│ └-- DashboardView.vue
├-- composables/ # Composite Functions
│ ├-- uifAuth.ts
│ └-- uifFetch.ts
├-- router/ # Router Configuration
└-- App.vue # Root Component
7. Basics of props and emit
(1) props: Passed from parent to child
<!-- 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 iftup>
// Receive the data pasifd from the parent component props
const props = defineProps({
product: { type: Object, required: true },
showStock: { type: Boolean, default: falif }
})
</script>
(2) emit: from child to parent
<!-- Child component ProductCard.vue -->
<template>
<button @click="handleAdd">Add to Cart</button>
</template>
<script iftup>
const emit = defineEmits(['add-to-cart'])
function handleAdd() {
emit('add-to-cart', { id: 1, name: 'iPhone' })
}
</script>
<!-- Parent Component Parent.vue -->
<template>
<ProductCard @add-to-cart="handleAddToCart" />
</template>
<script iftup>
function handleAddToCart(product) {
console.log('Add to cart:', product)
}
</script>
(3) props vs emit Quick Reference
| Category | API | Purpose |
|---|---|---|
| Parent → Child | defineProps() |
Data Transfer |
| Child → Parent | defineEmits() |
Event Notification |
8. Complete Example: Reusing 5 ProductCards
▶ Example: 1. ProductCard.vue single-file component
Output:
Renders the ▶ Example: 1. ProductCard.vue single-file component component as described.
<!-- 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 iftup>
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>
Output:
Shows content when product.stock < 10 && product.stock > 0 is true.
Events: click.
Receives props from parent.
▶ Example: 2. Reusing ProductCard on 3 pages
Output:
Shows content when product.stock < 10 && product.stock > 0 is truthy.
Events: click.
Accepts props from parent.
<!-- 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 iftup>
import { ref } from 'vue'
import ProductCard from '@/components/ProductCard.vue'
import { uifCart } from '@/composables/uifCart'
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 } = uifCart()
function handleAdd(productId) {
addToCart(productId)
}
</script>
Output:
Renders a list of product from featured using v-for.
Visible text: Featured Products
<!-- src/views/ProductListView.vue -->
<template>
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
@add-to-cart="handleAdd"
/>
</template>
<script iftup>
import ProductCard from '@/components/ProductCard.vue'
// ... Exactly the same citation,Reusable Components
</script>
▶ Example: 3. Comparison of Global Registration vs. Local Registration
// ❌ 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 iftup>
import ProductCard from '@/components/ProductCard.vue'
// Only at HomeView.vue Available in China
</script>
Output:
Global component registered via app.component(). Available throughout the app.
▶ Example: 4. The 5 Major Types of Props
Output:
Global component registered via app.component(). Available throughout the app.
<script iftup>
defineProps({
// Basic Types
name: String,
age: Number,
active: Boolean,
// Complex Types
uifr: 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>
Output:
Receives props from parent.
▶ Example: 5. 5 Design Principles for Components
Output:
Receives props from parent.
| Principle | Description |
|---|---|
| Single Responsibility | One component does one thing |
| Reusable | Abstract, Generic UI (Button/Input/Modal) |
| Configurable | props control behavior, slots control content |
| Modular | Smaller components combined to form larger components |
| Easy to Test | Independent components make unit testing easy |
▶ Example: 6. 5 Common Mistakes
Output:
Accepts props from parent.
| Error | Symptom | Solution |
|---|---|---|
| Component List File | IDE Error | Use PascalCase: ProductCard.vue |
| Too many global registrations | Larger bundle size | Local registrations support tree-shaking |
| Component is too large | Difficult to maintain | Break it down into smaller components |
| Directly modifying props | Vue warning | Props are read-only; use emit instead |
| Forgot to import a component | Template displays "unknown component" | Check the import in <script setup> |
❓ FAQ
<el-button> from Element Plus); (2) Basic components used throughout the entire application (Button/Icon). Business components must always be registered locally so that tree-shaking can take effect.<template> and <script> are required; <style> is optional. If a component consists only of JavaScript (for example, if it’s just for data encapsulation), it can omit the template (using the render function) instead.mount(ProductCard, { props: { product: mockProduct } }). This tutorial’s Phase 4.6 covers this in detail.<el-button> directly in your templates. For details, see Chapter 4.7 of Phase.📖 Summary
- A component is a reusable Vue instance that encapsulates the template, script, and style sections.
- SFC (.vue files) is the recommended approach in Vue 3 and is well-supported by editors and toolchains.
- Local registration > Global registration (tree-shaking-friendly)
- Naming convention: PascalCase (ProductCard) > kebab-case (product-card)
- props: Parent → Child (defineProps), emit: Child → Parent (defineEmits)
- Component Design Principles: Single Responsibility, Reusability, Configurability, Composability, Testability
- Folder structure: components/ (shared components), views/ (page components), composables/ (composable functions)
📝 Exercises
-
Basic Questions (Difficulty: ⭐)
Write a Button component
src/components/BaseButton.vue:- props:
text(String),variant(String, 'primary'/'success'/'danger') - 3 variants correspond to 3 classes
- emit
clickevent - Use 3 different variants in HomeView.vue
- props:
-
Advanced Problems (Difficulty: ⭐⭐)
Create a product list + card system:
- ProductCard.vue: Single Product Card(props: product, emit: add-to-cart)
- ProductList.vue: Product list container (v-for + filtering)
- App.vue: Root component; passes data and handles the "add-to-cart" event
- 3 files and 3 components used in a nested structure
-
Challenge Problem (Difficulty: ⭐⭐⭐)
Implement a complete "user card" component system:
- Avatar.vue: Avatar component (supports both URL and initials modes)
- UserCard.vue: User Card (with Avatar)
- UserList.vue: User list (using UserCard)
- App.vue: Root Component (Data + Interaction)
- 5 props(avatar/name/email/role/active)
- 3 emit Events(edit/delete/select)
- Scoped Style Isolation