Vue 元件基礎
元件(Component)是 Vue 最強大的功能之一。元件允許我們將 UI 分割成獨立、可重複使用的小單位,並且可以對每個單位進行獨立的思考和開發。
什麼是元件?
在 Vue 中,一個元件本質上是一個擁有預定義選項的 Vue 實例。你可以把元件想像成自訂的 HTML 元素,可以在模板中重複使用。
例如,你可能會有這樣的元件結構:
App
├── Header
├── Sidebar
│ ├── MenuItem
│ └── MenuItem
├── Content
│ ├── Article
│ └── Article
└── Footer
單文件元件(Single-File Components, SFC)
在使用建構工具的 Vue 專案中,我們通常使用一種特殊的檔案格式 .vue,稱為單文件元件(SFC)。
一個 .vue 檔案包含三個部分:
<!-- MyComponent.vue -->
<script setup>
// JavaScript 邏輯
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<!-- HTML 模板 -->
<button @click="count++">
點擊次數:{{ count }}
</button>
</template>
<style scoped>
/* CSS 樣式 */
button {
font-size: 16px;
padding: 10px 20px;
}
</style>
SFC 的優點
- 關注點分離:模板、邏輯、樣式分開,但又在同一個檔案中
- 作用域樣式:
scoped屬性讓樣式只作用於當前元件 - 建構工具優化:可以使用預處理器(Sass、TypeScript 等)
- 熱更新:開發時修改會立即反映在瀏覽器中
定義元件
使用 <script setup>(推薦)
<script setup> 是 Vue 3.2+ 推薦的寫法,更簡潔且效能更好:
<!-- ButtonCounter.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">點擊了 {{ count }} 次</button>
</template>
在 <script setup> 中,頂層的變數、函式、import 的內容都會自動暴露給模板使用,不需要額外的 return。
不使用 <script setup>
如果需要更多控制,也可以使用傳統的 setup() 函式:
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
// 需要 return 才能在模板中使用
return {
count,
increment
}
}
}
</script>
<template>
<button @click="increment">點擊了 {{ count }} 次</button>
</template>
使用元件
要使用一個元件,首先需要引入它,然後在模板中使用:
<!-- App.vue -->
<script setup>
import ButtonCounter from './components/ButtonCounter.vue'
</script>
<template>
<h1>這是我的 App</h1>
<!-- 使用元件 -->
<ButtonCounter />
<!-- 可以多次使用同一個元件 -->
<ButtonCounter />
<ButtonCounter />
</template>
每個元件實例都有自己獨立的狀態。當你點擊其中一個按鈕時,只有那個按鈕的計數會增加。
元件命名慣例
Vue 元件有兩種命名風格:
- PascalCase(推薦):
<ButtonCounter /> - kebab-case:
<button-counter />
在 SFC 中,推薦使用 PascalCase,因為:
- 可以和原生 HTML 元素區分開來
- 與 JavaScript 類別的命名慣例一致
元件註冊
局部註冊(推薦)
在 <script setup> 中,引入的元件可以直接在模板中使用,這稱為局部註冊:
<script setup>
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
</script>
<template>
<ComponentA />
<ComponentB />
</template>
局部註冊的好處:
- Tree-shaking:未使用的元件不會被打包
- 依賴關係明確:可以清楚知道元件從哪裡來
全域註冊
如果某個元件需要在多個地方使用,可以全域註冊:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import GlobalButton from './components/GlobalButton.vue'
const app = createApp(App)
// 全域註冊
app.component('GlobalButton', GlobalButton)
app.mount('#app')
全域註冊後,可以在任何元件的模板中使用,不需要額外引入:
<template>
<!-- 任何元件中都可以直接使用 -->
<GlobalButton />
</template>
傳遞 Props
元件可以接收外部傳入的資料,稱為 props:
<!-- BlogPost.vue -->
<script setup>
// 定義接收的 props
defineProps(['title', 'author'])
</script>
<template>
<article>
<h2>{{ title }}</h2>
<p>作者:{{ author }}</p>
</article>
</template>
父元件傳遞 props:
<!-- 父元件 -->
<script setup>
import BlogPost from './BlogPost.vue'
</script>
<template>
<BlogPost title="Vue 入門教學" author="小明" />
<BlogPost title="Composition API 詳解" author="小華" />
</template>
更多 Props 的用法請參考 Props 屬性傳遞。
監聽事件
子元件可以向父元件發送事件:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
// 定義發送的事件
const emit = defineEmits(['enlarge-text'])
function handleClick() {
emit('enlarge-text')
}
</script>
<template>
<article>
<h2>{{ title }}</h2>
<button @click="handleClick">放大字體</button>
</article>
</template>
父元件監聽事件:
<script setup>
import { ref } from 'vue'
import BlogPost from './BlogPost.vue'
const fontSize = ref(16)
</script>
<template>
<div :style="{ fontSize: fontSize + 'px' }">
<BlogPost
title="Vue 入門"
@enlarge-text="fontSize += 2"
/>
</div>
</template>
更多事件的用法請參考 事件處理與 Emit。
插槽(Slots)
插槽讓父元件可以傳遞模板內容給子元件:
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>注意!</strong>
<slot></slot>
</div>
</template>
<style scoped>
.alert-box {
padding: 15px;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
</style>
使用插槽:
<template>
<AlertBox>
這是一則重要的警告訊息!
</AlertBox>
</template>
渲染結果:
<div class="alert-box">
<strong>注意!</strong>
這是一則重要的警告訊息!
</div>
更多插槽的用法請參考 Slots 插槽。
動態元件
有時候我們需要在多個元件之間動態切換,可以使用 <component> 元素和 :is 屬性:
<script setup>
import { ref, shallowRef } from 'vue'
import TabHome from './TabHome.vue'
import TabPosts from './TabPosts.vue'
import TabArchive from './TabArchive.vue'
const tabs = {
home: TabHome,
posts: TabPosts,
archive: TabArchive
}
const currentTab = ref('home')
</script>
<template>
<div class="tabs">
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="{ active: currentTab === tab }"
@click="currentTab = tab"
>
{{ tab }}
</button>
</div>
<!-- 動態元件 -->
<component :is="tabs[currentTab]"></component>
</template>
更多動態元件的用法請參考 動態元件。
元件的組織結構建議
一個典型的 Vue 專案元件結構:
src/
├── components/ # 可重複使用的通用元件
│ ├── ui/ # UI 基礎元件
│ │ ├── BaseButton.vue
│ │ ├── BaseInput.vue
│ │ └── BaseCard.vue
│ ├── layout/ # 佈局元件
│ │ ├── TheHeader.vue
│ │ ├── TheSidebar.vue
│ │ └── TheFooter.vue
│ └── feature/ # 功能性元件
│ ├── UserProfile.vue
│ └── SearchBar.vue
├── views/ # 頁面級元件(配合 Vue Router)
│ ├── HomeView.vue
│ ├── AboutView.vue
│ └── UserView.vue
└── App.vue # 根元件
元件命名建議
- 基礎元件:以
Base、App或V開頭,如BaseButton、AppTable - 單例元件:以
The開頭,如TheHeader、TheSidebar - 緊密耦合:以父元件名為前綴,如
TodoList、TodoListItem
元件的 CSS
Scoped CSS
使用 scoped 屬性讓樣式只作用於當前元件:
<style scoped>
/* 只會影響這個元件內的 .title */
.title {
color: red;
}
</style>
深度選擇器
如果需要影響子元件的樣式,可以使用 :deep() 選擇器:
<style scoped>
/* 影響子元件內的 .child-class */
:deep(.child-class) {
color: blue;
}
</style>
CSS Modules
Vue 也支援 CSS Modules:
<template>
<p :class="$style.red">這是紅色文字</p>
</template>
<style module>
.red {
color: red;
}
</style>
使用預處理器
可以在 <style> 標籤指定預處理器:
<style lang="scss" scoped>
$primary-color: #42b883;
.button {
background-color: $primary-color;
&:hover {
background-color: darken($primary-color, 10%);
}
}
</style>
npm install -D sass。