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 {
    // 非同期操作のシミュレーション
    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;
  --van-button-border-radius: 20px;
  font-weight: 600;
  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. コンポーネント設計原則

    • 単一責任原則:各コンポーネントは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 の開発には多くのツールとテクニックが必要です。以上のガイドを参考に、Vue 3 の Composition API を活用して、Vant のコンポーネントを効果的に使用してください。

型安全を保証する TypeScript を使用し、パフォーマンスを最適化するために、Vue DevTools を活用してください。さらに、コンポーネントの品質を保証するユニットテストを書き、ドキュメントを及时に更新してください。

以上のベストプラクティスを遵守することで、Vue 3 + Vant の開発を効率的かつ品質の高いものにすることができます。

📚 関連コンテンツ

さらに詳しく知りたいですか?これらのドキュメントがあなたの開発の旅をより便利にします:

Vant に基づく企業向けモバイルソリューション