Skip to content

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

🚀 最佳實踐總結

🎯 開發規範

  1. 元件設計原則

    • 單一職責:每個元件只負責一個功能
    • 可重複使用性:透過 props 和 slots 提供靈活性
    • 類型安全:使用 TypeScript 確保類型安全
    • 效能最佳化:合理使用快取和懶載入
  2. 程式碼組織 📁

    • 使用 Composition API 組織邏輯
    • 合理拆分 composables
    • 統一的類型定義管理
    • 清晰的檔案夾結構
  3. 效能最佳化

    • 按需引入元件和樣式
    • 使用非同步元件和 Suspense
    • 合理使用 v-memo 和 v-once
    • 避免不必要的響應式資料

📂 專案結構推薦

src/
├── components/          # 公共元件
│   ├── base/           # 基礎元件
│   ├── business/       # 業務元件
│   └── layout/         # 佈局元件
├── composables/        # 組合式函數
├── types/              # 類型定義
├── utils/              # 工具函數
├── styles/             # 樣式檔案
│   ├── variables.css   # CSS 變數
│   ├── mixins.scss     # Sass 混入
│   └── themes/         # 主題檔案
├── views/              # 頁面元件
└── router/             # 路由配置

💡 開發技巧

  1. 善用 Vue DevTools 🔧:除錯 Composition API 和響應式資料
  2. 類型提示 📝:充分利用 TypeScript 的智慧提示
  3. 效能監控 📊:使用 Vue DevTools 的效能面板
  4. 元件測試 🧪:編寫單元測試確保元件品質
  5. 文件維護 📖:保持元件文件的即時更新

🎉 總結

透過以上指南,你已經掌握了 Vue 3 + Vant 開發的精髓!從 Composition API 的靈活運用到 TypeScript 的類型安全,從效能最佳化到主題客製化,這些技能將幫助你建構出色的行動端應用程式。

記住,優秀的程式碼不僅僅是功能的實現,更是藝術的體現。保持學習的熱情,持續最佳化你的程式碼,讓每一行程式碼都閃閃發光!✨

📚 相關內容

想要了解更多?這些文件將為你的開發之旅提供更多幫助:

基於Vant構建的企業級移動端解決方案