🚀 Vue 3 整合指南 - 現代化開發的完美搭檔!
歡迎來到 Vue 3 + Vant 的精彩世界!🎉 這裡有最全面的整合指南,帶你深入探索 Vue 3 的強大特性,與 Vant 元件庫完美融合,打造令人驚豔的行動端應用程式。
從 Composition API 到 TypeScript 支援,從效能最佳化到主題客製化,我們將一步步引導你掌握現代化前端開發的精髓。讓我們一起開啟這段激動人心的開發之旅吧!✨
🚀 Vue 3 新特性支援
Composition API 整合
Vant 4.x 完全支援 Vue 3 的 Composition API,讓元件邏輯更加清晰和可複用。
基礎用法範例
vue
<script setup>
import { ref, computed } from 'vue'
import { showToast, showDialog } from 'vant'
// 響應式資料
const count = ref(0)
const loading = ref(false)
const userInfo = ref({
name: '張三',
age: 25,
city: '北京'
})
// 計算屬性
const displayInfo = computed(() => {
return `${userInfo.value.name} (${userInfo.value.age}歲) - ${userInfo.value.city}`
})
// 方法
const handleIncrement = async () => {
loading.value = true
// 模擬非同步操作
await new Promise(resolve => setTimeout(resolve, 1000))
count.value++
loading.value = false
showToast(`目前計數: ${count.value}`)
}
const handleReset = () => {
showDialog({
title: '確認重置',
message: '確定要重置計數器嗎?',
}).then(() => {
count.value = 0
showToast('已重置')
}).catch(() => {
showToast('已取消')
})
}
</script>
<template>
<div class="demo-container">
<!-- 使用者資訊展示 -->
<van-cell-group title="使用者資訊">
<van-cell title="基本資訊" :value="displayInfo" />
<van-cell title="目前計數" :value="count" />
</van-cell-group>
<!-- 操作按鈕 -->
<div class="button-group">
<van-button
type="primary"
:loading="loading"
@click="handleIncrement"
block
>
增加計數
</van-button>
<van-button
type="danger"
:disabled="count === 0"
@click="handleReset"
block
>
重置計數
</van-button>
</div>
</div>
</template>
<style scoped>
.demo-container {
padding: 16px;
}
.button-group {
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
</style>響應式系統深度整合
使用 reactive 管理複雜狀態
vue
<script setup>
import { reactive, computed, watch } from 'vue'
import { showNotify } from 'vant'
// 複雜狀態管理
const state = reactive({
user: {
name: '',
email: '',
phone: ''
},
form: {
loading: false,
errors: {}
},
settings: {
notifications: true,
darkMode: false
}
})
// 表單驗證
const isFormValid = computed(() => {
return state.user.name &&
state.user.email &&
state.user.phone
})
// 監聽設定變化
watch(() => state.settings.darkMode, (newValue) => {
document.documentElement.classList.toggle('dark', newValue)
showNotify({
type: 'success',
message: `已切換到${newValue ? '深色' : '淺色'}模式`
})
})
// 表單提交
const handleSubmit = async () => {
if (!isFormValid.value) {
showNotify({ type: 'warning', message: '請填寫完整資訊' })
return
}
state.form.loading = true
try {
// 模擬API呼叫
await new Promise(resolve => setTimeout(resolve, 2000))
showNotify({ type: 'success', message: '儲存成功' })
} catch (error) {
showNotify({ type: 'danger', message: '儲存失敗' })
} finally {
state.form.loading = false
}
}
</script>
<template>
<div class="form-container">
<van-form @submit="handleSubmit">
<van-cell-group title="個人資訊">
<van-field
v-model="state.user.name"
name="name"
label="姓名"
placeholder="請輸入姓名"
:rules="[{ required: true, message: '請填寫姓名' }]"
/>
<van-field
v-model="state.user.email"
name="email"
label="電子郵件"
placeholder="請輸入電子郵件"
:rules="[
{ required: true, message: '請填寫電子郵件' },
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '電子郵件格式不正確' }
]"
/>
<van-field
v-model="state.user.phone"
name="phone"
label="手機號碼"
placeholder="請輸入手機號碼"
:rules="[
{ required: true, message: '請填寫手機號碼' },
{ pattern: /^1[3-9]\d{9}$/, message: '手機號碼格式不正確' }
]"
/>
</van-cell-group>
<van-cell-group title="設定">
<van-cell title="通知推送">
<template #right-icon>
<van-switch v-model="state.settings.notifications" />
</template>
</van-cell>
<van-cell title="深色模式">
<template #right-icon>
<van-switch v-model="state.settings.darkMode" />
</template>
</van-cell>
</van-cell-group>
<div class="submit-section">
<van-button
type="primary"
native-type="submit"
:loading="state.form.loading"
:disabled="!isFormValid"
block
>
儲存資訊
</van-button>
</div>
</van-form>
</div>
</template>
<style scoped>
.form-container {
padding: 16px;
}
.submit-section {
margin-top: 24px;
}
</style>🎯 TypeScript 完美支援
元件類型定義
typescript
// types/user.ts
export interface User {
id: number
name: string
email: string
avatar?: string
phone?: string
status: 'active' | 'inactive' | 'pending'
}
export interface UserFormData {
name: string
email: string
phone: string
}
// types/components.ts
export interface ButtonProps {
type?: 'primary' | 'success' | 'warning' | 'danger' | 'default'
size?: 'large' | 'normal' | 'small' | 'mini'
loading?: boolean
disabled?: boolean
block?: boolean
}TypeScript 元件範例
vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { User, UserFormData } from '@/types/user'
// Props 介面定義
interface Props {
user?: User
readonly?: boolean
showAvatar?: boolean
}
// 預設值設定
const props = withDefaults(defineProps<Props>(), {
readonly: false,
showAvatar: true
})
// Emits 類型定義
interface Emits {
(e: 'update', data: UserFormData): void
(e: 'delete', id: number): void
(e: 'save', user: User): void
}
const emit = defineEmits<Emits>()
// 響應式資料
const formData = ref<UserFormData>({
name: props.user?.name || '',
email: props.user?.email || '',
phone: props.user?.phone || ''
})
const loading = ref<boolean>(false)
// 計算屬性
const isFormValid = computed((): boolean => {
return !!(formData.value.name &&
formData.value.email &&
formData.value.phone)
})
const avatarUrl = computed((): string => {
return props.user?.avatar || '/default-avatar.png'
})
// 方法
const handleSave = async (): Promise<void> => {
if (!isFormValid.value) return
loading.value = true
try {
const updatedUser: User = {
id: props.user?.id || Date.now(),
...formData.value,
status: 'active'
}
emit('save', updatedUser)
} finally {
loading.value = false
}
}
const handleDelete = (): void => {
if (props.user?.id) {
emit('delete', props.user.id)
}
}
// 暴露給父元件的方法
defineExpose({
validate: () => isFormValid.value,
reset: () => {
formData.value = {
name: '',
email: '',
phone: ''
}
}
})
</script>
<template>
<div class="user-form">
<!-- 頭像顯示 -->
<div v-if="showAvatar" class="avatar-section">
<van-image
:src="avatarUrl"
width="80"
height="80"
round
fit="cover"
/>
</div>
<!-- 表單內容 -->
<van-form @submit="handleSave">
<van-field
v-model="formData.name"
label="姓名"
placeholder="請輸入姓名"
:readonly="readonly"
:rules="[{ required: true, message: '請填寫姓名' }]"
/>
<van-field
v-model="formData.email"
label="電子郵件"
placeholder="請輸入電子郵件"
:readonly="readonly"
:rules="[
{ required: true, message: '請填寫電子郵件' },
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '電子郵件格式不正確' }
]"
/>
<van-field
v-model="formData.phone"
label="手機號碼"
placeholder="請輸入手機號碼"
:readonly="readonly"
:rules="[
{ required: true, message: '請填寫手機號碼' },
{ pattern: /^1[3-9]\d{9}$/, message: '手機號碼格式不正確' }
]"
/>
<!-- 操作按鈕 -->
<div v-if="!readonly" class="action-buttons">
<van-button
type="primary"
:loading="loading"
:disabled="!isFormValid"
@click="handleSave"
block
>
儲存
</van-button>
<van-button
v-if="user?.id"
type="danger"
plain
@click="handleDelete"
block
>
刪除
</van-button>
</div>
</van-form>
</div>
</template>
<style scoped>
.user-form {
padding: 16px;
}
.avatar-section {
display: flex;
justify-content: center;
margin-bottom: 24px;
}
.action-buttons {
margin-top: 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
</style>🔧 效能最佳化策略
按需引入配置
自動按需引入(推薦)
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
// 產生類型定義檔案
dts: true,
// 自訂元件目錄
dirs: ['src/components'],
// 包含的檔案副檔名
extensions: ['vue', 'ts'],
// 深度搜尋子目錄
deep: true
})
],
// 建置最佳化
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'vant-vendor': ['vant'],
'utils-vendor': ['lodash-es', 'dayjs']
}
}
}
}
})手動按需引入
javascript
// main.js
import { createApp } from 'vue'
import {
Button,
Cell,
CellGroup,
Field,
Form,
Toast,
Dialog,
Notify
} from 'vant'
// 引入對應樣式
import 'vant/es/button/style'
import 'vant/es/cell/style'
import 'vant/es/cell-group/style'
import 'vant/es/field/style'
import 'vant/es/form/style'
import 'vant/es/toast/style'
import 'vant/es/dialog/style'
import 'vant/es/notify/style'
const app = createApp(App)
app.use(Button)
app.use(Cell)
app.use(CellGroup)
app.use(Field)
app.use(Form)
app.use(Toast)
app.use(Dialog)
app.use(Notify)
app.mount('#app')元件懶載入
vue
<script setup>
import { defineAsyncComponent } from 'vue'
// 非同步元件
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
// 帶載入狀態的非同步元件
const AsyncUserList = defineAsyncComponent({
loader: () => import('./components/UserList.vue'),
loadingComponent: () => h('van-loading', { type: 'spinner' }),
errorComponent: () => h('van-empty', { description: '載入失敗' }),
delay: 200,
timeout: 3000
})
</script>
<template>
<div>
<Suspense>
<template #default>
<HeavyComponent />
<AsyncUserList />
</template>
<template #fallback>
<van-skeleton :row="3" />
</template>
</Suspense>
</div>
</template>🎨 主題客製化與樣式
CSS 變數客製化
css
/* 全域主題變數 */
:root {
/* 主色調 */
--van-primary-color: #1989fa;
--van-primary-color-dark: #0960bd;
--van-primary-color-light: #66b1ff;
/* 功能色 */
--van-success-color: #07c160;
--van-warning-color: #ff976a;
--van-danger-color: #ee0a24;
/* 文字顏色 */
--van-text-color: #323233;
--van-text-color-2: #646566;
--van-text-color-3: #969799;
/* 背景顏色 */
--van-background-color: #f7f8fa;
--van-background-color-light: #fafafa;
/* 邊框 */
--van-border-color: #ebedf0;
--van-border-width: 1px;
/* 字體大小 */
--van-font-size-xs: 10px;
--van-font-size-sm: 12px;
--van-font-size-md: 14px;
--van-font-size-lg: 16px;
/* 間距 */
--van-padding-base: 4px;
--van-padding-xs: 8px;
--van-padding-sm: 12px;
--van-padding-md: 16px;
--van-padding-lg: 24px;
/* 圓角 */
--van-border-radius-sm: 2px;
--van-border-radius-md: 4px;
--van-border-radius-lg: 8px;
}
/* 深色模式 */
@media (prefers-color-scheme: dark) {
:root {
--van-primary-color: #4fc3f7;
--van-text-color: #ffffff;
--van-text-color-2: #cccccc;
--van-background-color: #1a1a1a;
--van-background-color-light: #2d2d2d;
--van-border-color: #333333;
}
}元件級樣式客製化
vue
<script setup>
import { ref } from 'vue'
const theme = ref('light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme.value)
}
</script>
<template>
<div class="themed-container" :data-theme="theme">
<!-- 自訂按鈕樣式 -->
<van-button class="custom-button" type="primary">
自訂按鈕
</van-button>
<!-- 自訂卡片樣式 -->
<van-card class="custom-card" title="自訂卡片">
<template #footer>
<van-button size="small" @click="toggleTheme">
切換主題
</van-button>
</template>
</van-card>
</div>
</template>
<style scoped>
.themed-container {
padding: 16px;
}
/* 自訂按鈕樣式 */
.custom-button {
--van-button-primary-background-color: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--van-button-primary-border-color: transparent;
border-radius: 20px;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
}
.custom-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
/* 自訂卡片樣式 */
.custom-card {
--van-card-background-color: var(--van-background-color-light);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* 深色主題適配 */
[data-theme="dark"] .custom-card {
--van-card-background-color: #2d2d2d;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}
</style>🚀 最佳實踐總結
🎯 開發規範
元件設計原則 ⭐
- 單一職責:每個元件只負責一個功能
- 可重複使用性:透過 props 和 slots 提供靈活性
- 類型安全:使用 TypeScript 確保類型安全
- 效能最佳化:合理使用快取和懶載入
程式碼組織 📁
- 使用 Composition API 組織邏輯
- 合理拆分 composables
- 統一的類型定義管理
- 清晰的檔案夾結構
效能最佳化 ⚡
- 按需引入元件和樣式
- 使用非同步元件和 Suspense
- 合理使用 v-memo 和 v-once
- 避免不必要的響應式資料
📂 專案結構推薦
src/
├── components/ # 公共元件
│ ├── base/ # 基礎元件
│ ├── business/ # 業務元件
│ └── layout/ # 佈局元件
├── composables/ # 組合式函數
├── types/ # 類型定義
├── utils/ # 工具函數
├── styles/ # 樣式檔案
│ ├── variables.css # CSS 變數
│ ├── mixins.scss # Sass 混入
│ └── themes/ # 主題檔案
├── views/ # 頁面元件
└── router/ # 路由配置💡 開發技巧
- 善用 Vue DevTools 🔧:除錯 Composition API 和響應式資料
- 類型提示 📝:充分利用 TypeScript 的智慧提示
- 效能監控 📊:使用 Vue DevTools 的效能面板
- 元件測試 🧪:編寫單元測試確保元件品質
- 文件維護 📖:保持元件文件的即時更新
🎉 總結
透過以上指南,你已經掌握了 Vue 3 + Vant 開發的精髓!從 Composition API 的靈活運用到 TypeScript 的類型安全,從效能最佳化到主題客製化,這些技能將幫助你建構出色的行動端應用程式。
記住,優秀的程式碼不僅僅是功能的實現,更是藝術的體現。保持學習的熱情,持續最佳化你的程式碼,讓每一行程式碼都閃閃發光!✨
📚 相關內容
想要了解更多?這些文件將為你的開發之旅提供更多幫助: