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



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:

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

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

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 iftup>
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 → reused 30 times. To change the button text, you only need to make the change in one place.

(3) Revenue

After componentization:



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.

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 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.

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

JS
// ❌ 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:



5. Component Registration

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')
VUE
<!-- 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

VUE
<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

TEXT 📖 Display only
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

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

VUE
<!-- 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>
VUE
<!-- 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:

TEXT 📖 Display only
Renders the ▶ Example: 1. ProductCard.vue single-file component component as described.
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 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:

TEXT 📖 Display only
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:

TEXT 📖 Display only
Shows content when product.stock < 10 && product.stock > 0 is truthy.
Events: click.
Accepts props from parent.
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 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:

TEXT 📖 Display only
Renders a list of product from featured using v-for.
Visible text: Featured Products
VUE
<!-- 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

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 iftup>
import ProductCard from '@/components/ProductCard.vue'
// Only at HomeView.vue Available in China
</script>
▶ Try it Yourself

Output:

TEXT 📖 Display only
Global component registered via app.component(). Available throughout the app.

▶ Example: 4. The 5 Major Types of Props

Output:

TEXT 📖 Display only
Global component registered via app.component(). Available throughout the app.
VUE
<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:

TEXT 📖 Display only
Receives props from parent.

▶ Example: 5. 5 Design Principles for Components

Output:

TEXT 📖 Display only
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:

TEXT 📖 Display only
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

Q When should global registration be used?
A Only in two situations: (1) Plug-in components (such as <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.
Q Does SFC need to have all three parts?
A <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.
Q What is an appropriate size for a component file?
A We recommend keeping it under 200 lines. If it exceeds 300 lines, consider splitting it up. The simpler the component, the easier it is to maintain.
Q SFC uses the .vue extension. How do other tools recognize it?
A Vite, Webpack, and Vue CLI all recognize the .vue extension by default. For VS Code, install the "Vue - Official" extension.
Q How do I test a single component?
A Use Vitest + Vue Test Utils (@vue/test-utils): mount(ProductCard, { props: { product: mockProduct } }). This tutorial’s Phase 4.6 covers this in detail.
Q How do I use the component library (Element Plus)?
A Install via npm → Register globally in main.js → Use <el-button> directly in your templates. For details, see Chapter 4.7 of Phase.

📖 Summary


📝 Exercises

  1. 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 click event
    • Use 3 different variants in HomeView.vue
  2. 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
  3. Challenge Problem (Difficulty: ⭐⭐⭐)

    Implement a complete "user card" component system:

    1. Avatar.vue: Avatar component (supports both URL and initials modes)
    2. UserCard.vue: User Card (with Avatar)
    3. UserList.vue: User list (using UserCard)
    4. App.vue: Root Component (Data + Interaction)
    5. 5 props(avatar/name/email/role/active)
    6. 3 emit Events(edit/delete/select)
    7. Scoped Style Isolation
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏