404 Not Found

404 Not Found


nginx

Vue 3 の動的コンポーネントと非同期コンポーネント:コンポーネント + Suspense

動的コンポーネントを使用すると、単一の <component> タグを使って異なるコンポーネントを動的にレンダリングでき、データに基づいてタブ、モーダル、ルートビューを切り替えることができます。非同期コンポーネントを使用すると、必要に応じてコンポーネントを読み込むことができ、最初の画面では必要なコードのみを読み込むことでパフォーマンスを向上させることができます。

Vue 3の<Suspense>実験的コンポーネントは、非同期コンポーネントの読み込み状態を処理するために特別に設計されており、v-if="loading"を手動で記述する必要がなくなります。このレッスンでは、3つの主要なAPIを習得できます。

1. 学習内容


2. 1つのタブに5つのv-if文が並ぶ悪夢

(1) 課題:5つのタブ、10個のネストされた v-if ディレクティブ

アリスはタブ付きの管理用ダッシュボードを作成しました:

VUE
<!-- ❌ The "Flip" Version: 5 Tabs with 5 v-if -->
<template>
  <div class="tabs">
    <button @click="currentTab = 'home'">Home</button>
    <button @click="currentTab = 'profile'">Profile</button>
    <button @click="currentTab = 'settings'">Settings</button>
    
    <div v-if="currentTab === 'home'">
      <HomeTab />
    </div>
    <div v-else-if="currentTab === 'profile'">
      <ProfileTab />
    </div>
    <div v-else-if="currentTab === 'settings'">
      <SettingsTab />
    </div>
  </div>
</template>

プロダクトマネージャーのチャーリー:

「アリス、タブをあと5つ増やして。v-if-elseが10行もあると保守性が悪くなる。動的コンポーネントを使って。」

(2) Vueの動的コンポーネントによる解決策:1つの<component>で5つのタブを切り替える

VUE
<!-- ✅ Correct Version: 1 <component> Switch -->
<template>
  <div class="tabs">
    <button 
      v-for="tab in tabs" 
      :key="tab.name"
      :class="{ active: currentTab === tab.name }"
      @click="currentTab = tab.name"
    >
      {{ tab.label }}
    </button>
    
    <!-- ✅ 1 <component> Dynamic Tag Switching -->
    <component :is="currentTabComponent" />
  </div>
</template>

<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'

// Synchronization Components
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'

// Asynchronous Components(Laden on Demand)
const SettingsTab = defineAsyncComponent(() => import('./tabs/SettingsTab.vue'))

const tabs = [
  { name: 'home', label: 'Home' },
  { name: 'profile', label: 'Profile' },
  { name: 'settings', label: 'Settings' }
]

const currentTab = ref('home')

// Computed Properties:According to currentTab Determine which component to display
const currentTabComponent = computed(() => {
  return { home: HomeTab, profile: ProfileTab, settings: SettingsTab }[currentTab.value]
})
</script>

(3) 収益

動的コンポーネントを使用した後:


3. <component :is="..."> 基礎

(1) :is の5つの活用法

VUE
<!-- Writing Style 1:Component Name String(Registered globally) -->
<component :is="'HomeTab'" />

<!-- Writing Style 2:Component Object(Most Commonly Used) -->
<component :is="HomeTab" />

<!-- Writing Style 3:Dynamically Calculated Properties -->
<component :is="currentTabComponent" />

<!-- Writing Style 4:Asynchronous Component Object -->
<component :is="asyncComponent" />

<!-- Writing Style 5:Inline Component Object -->
<component :is="{ template: '<div>Inline</div>' }" />

(2) 完全な例

VUE
<!-- TabContainer.vue -->
<template>
  <div>
    <button 
      v-for="tab in tabs" 
      :key="tab.name"
      @click="currentTab = tab.name"
    >
      {{ tab.label }}
    </button>
    
    <!-- Dynamic Components -->
    <component :is="currentTabComponent" />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'
import SettingsTab from './tabs/SettingsTab.vue'

const tabs = [
  { name: 'home', label: 'Home' },
  { name: 'profile', label: 'Profile' },
  { name: 'settings', label: 'Settings' }
]

const currentTab = ref('home')

const currentTabComponent = computed(() => ({
  home: HomeTab,
  profile: ProfileTab,
  settings: SettingsTab
}[currentTab.value]))
</script>

(3) 5つの主な活用シナリオ

シーン ダイナミックコンポーネント
タブの切り替え
脚本・台詞
複数ステップのフォーム
ルートビュー ✅ (内部コンポーネント <router-view>)
テーマの切り替え(異なるレイアウト)

4. 非同期コンポーネント defineAsyncComponent

(1) なぜ非同期コンポーネントが必要なのでしょうか?

標準コンポーネントは、import時にすべてのコードを即座に読み込みます。リッチテキストエディタやチャートライブラリなどの大規模なコンポーネントは、最初の画面表示には必ずしも必要ではありませんが、読み込みを妨げてしまいます。非同期コンポーネントは必要に応じて読み込まれるため、最初の画面表示が高速化されます。

VUE
<script setup>
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from './LoadingSpinner.vue'

// Asynchronous Components:Only when using ChartEditor Load its code only when needed
const ChartEditor = defineAsyncComponent({
  // 1. Load Function
  loader: () => import('./ChartEditor.vue'),
  
  // 2. Display while loading
  loadingComponent: LoadingSpinner,
  
  // 3. Display when loading fails
  errorComponent: ErrorMessage,
  
  // 4. Load Delay(Prevent Flickering)
  delay: 200
})
</script>

<template>
  <ChartEditor v-if="showEditor" />
</template>

(2) 簡略化された表記法

VUE
<script setup>
import { defineAsyncComponent } from 'vue'

// ✅ Simplified Version:Only loader
const ChartEditor = defineAsyncComponent(() => import('./ChartEditor.vue'))
</script>

(3) 非同期コンポーネントに関する5つの主要なシナリオ

シナリオ 非同期読み込み
リッチテキストエディタ
チャートライブラリ (ECharts/D3)
大規模なフォーム(フィールド数が100以上)
モーダルポップアップ
ルートの遅延読み込み ✅ (Vue Router によって自動的に使用されます)

5. <Suspense> 非同期コンポーネントの取り扱い

(1) 「サスペンス」とは何か?

Suspenseコンポーネントは、Vue 3の非同期コンポーネント読み込みコーディネーターであり、非同期コンポーネントの読み込み状態やエラー状態を自動的に処理します。手動でv-if="loading"を書く手間とはもうおさらばです

VUE
<template>
  <!-- Suspense Automatically wait for asynchronous components to finish loading -->
  <Suspense>
    <!-- Asynchronous Components -->
    <ChartEditor :data="chartData" />
    
    <!-- Display while loading(Default Slot) -->
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

(2) 2スロット

VUE
<template>
  <Suspense>
    <!-- Default Slot:Asynchronous Content -->
    <AsyncComponent />
    
    <!-- Fallback Slot: Display while loading -->
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

(3) 5つの主な利点

利点 説明
シンプルさ 読み込み状態を手動でコーディングする必要がない
統合 複数の非同期コンポーネントの統合処理
ネスト ネストされたSuspenseに対応
エラー処理 onErrorCaptured と連携
SSR対応 サーバーサイドの自動待機

6. 動的コンポーネントとキープアライブキャッシュ

(1) デフォルトの動作:コンポーネントを切り替えると、それらは破棄され、再構築される

VUE
<template>
  <component :is="currentTabComponent" />
</template>

<!-- Switch Tab:
1. Legacy Components onBeforeUnmount
2. Legacy Components onUnmounted
3. New Component onBeforeMount
4. New Component onMounted
5. Data is reloaded every time you switch(Poor performance)
-->

(2) キープアライブキャッシュ(重複作成を防ぐため)

VUE
<template>
  <keep-alive>
    <component :is="currentTabComponent" />
  </keep-alive>
</template>

<!-- Switch Tab:
1. Legacy Components onDeactivated(Destroy without stopping)
2. New Component onActivated(Restore from Cache)
3. Data Retention,Scroll Position Retention
-->

(3) keep-alive の 3 つの主要な設定

VUE
<template>
  <!-- include:Cache only the specified components -->
  <keep-alive include="HomeTab,ProfileTab">
    <component :is="currentTabComponent" />
  </keep-alive>
  
  <!-- exclude:Do not cache the specified component -->
  <keep-alive exclude="SettingsTab">
    <component :is="currentTabComponent" />
  </keep-alive>
  
  <!-- max: Maximum 5 Cached -->
  <keep-alive :max="5">
    <component :is="currentTabComponent" />
  </keep-alive>
</template>

7. 完全な例:動的なタブと非同期読み込み

(1) ▶ サンプル:. ダイナミックタブの基本

VUE
<template>
  <div>
    <nav>
      <button
        v-for="tab in tabs"
        :key="tab.name"
        :class="{ active: currentTab === tab.name }"
        @click="currentTab = tab.name"
      >
        {{ tab.label }}
      </button>
    </nav>
    
    <component :is="currentTabComponent" />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import HomeTab from './tabs/HomeTab.vue'
import ProfileTab from './tabs/ProfileTab.vue'

const tabs = [
  { name: 'home', label: 'Home' },
  { name: 'profile', label: 'Profile' },
  { name: 'settings', label: 'Settings' }
]
const currentTab = ref('home')

const currentTabComponent = computed(() => ({
  home: HomeTab,
  profile: ProfileTab,
  settings: SettingsTab
}[currentTab.value]))
</script>
▶ 試してみよう

(2) ▶ サンプル:. 非同期コンポーネント + Suspense

VUE
<!-- App.vue -->
<template>
  <button @click="show = !show">Toggle</button>
  
  <Suspense v-if="show">
    <!-- Asynchronous Components(Do not load on the first screen,Load only when clicked) -->
    <HeavyChart :data="data" />
    
    <template #fallback>
      <div>Loading chart...</div>
    </template>
  </Suspense>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue'

const show = ref(false)

// ✅ Asynchronous Components:News import,Laden on Demand
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const data = ref([1, 2, 3, 4, 5])
</script>
▶ 試してみよう

(3) ▶ サンプル:. 動的コンポーネントとキープアライブキャッシュ

VUE
<template>
  <div>
    <button v-for="tab in tabs" @click="currentTab = tab.name">
      {{ tab.label }}
    </button>
    
    <!-- keep-alive Cache: Preserve data when switching -->
    <keep-alive :max="3">
      <component :is="currentTabComponent" />
    </keep-alive>
  </div>
</template>
▶ 試してみよう

(4) ▶ サンプル:. :is の 5 つの使い方の比較

構文 使い方
文字列 <component :is="'HomeTab'"> グローバル登録(推奨されません)
コンポーネントオブジェクト <component :is="HomeTab"> 静的インポート(最も一般的)
非同期コンポーネントオブジェクト <component :is="AsyncComponent"> オンデマンド読み込み
計算プロパティ <component :is="currentTabComponent"> 動的切り替え
インラインオブジェクト <component :is="{ template: '...' }"> 簡単なシナリオ

(5) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス

エラー 症状 解決策
:は文字列ですが、グローバルに登録されていません 警告: 未知のコンポーネント コンポーネントオブジェクトを使用するか、まず登録してください
非同期コンポーネントにはフォールバックがない 読み込み中に画面が真っ白になる Suspenseでラップする
コンポーネントを切り替えてデータを再読み込み パフォーマンスの低下 キープアライブキャッシュを使用
非同期コンポーネントにおける未処理のエラー サイレントな失敗 onErrorCaptured との併用
動的コンポーネントに :key を追加し忘れた 状態の不整合 :key="tab.name" を追加

(6) ▶ サンプル:. 5つの主要なパフォーマンス比較

モード 最初の画面 切り替え 適用対象
直接インポート 低速 再読み込み ウィジェット
動的インポート 高速 最初の切り替え時に遅い 大規模なコンポーネント
サスペンス 高速 スムーズ ユーザー体験を最優先
キープアライブ 高速 0 リロード 頻繁な切り替え
非同期 + キープアライブ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ベストプラクティス

❓ よくある質問

Q ダイナミックコンポーネントとv-ifの違いは何ですか?
A v-ifの場合、条件が5つ、分岐が5つあっても、コンポーネントはそのまま丸ごと読み込まれます。一方、ダイナミックコンポーネントでは、単一の<component>タグを使用し、:is属性に基づいてコンポーネントを切り替えるため、より簡潔になります。また、非同期コンポーネントはオンデマンドで読み込むことも可能です。
Q defineAsyncComponentsetup の最上位レベルにある必要がありますか?
A はい。defineAsyncComponent(() => import('...'))<script setup> の最上位レベル(関数内ではなく)で呼び出さなければなりません。
Q Suspense は安定した機能ですか?
A Vue 3.2 以降では安定しています。安心してご利用いただけますが、本番環境ではフォールバック動作を徹底的にテストすることをお勧めします。
Q ダイナミックコンポーネントはプロップを渡すことができますか?
A はい。v-bind を直接使用します:<component :is="Comp" :prop1="x" :prop2="y" />
Q 非同期コンポーネントでエラーを捕捉するにはどうすればよいですか?
A errorComponent オプションと onErrorCaptured: { loader, errorComponent: ErrorComp } を使用します。Suspense 内では、コードを onErrorCaptured で囲むことができます。
Q keep-alive によってキャッシュされたコンポーネントデータは永続化されますか?
A キャッシュされている間はデータは保持され(破棄されません)。ただし、ページを更新したり、ルートを切り替えたりするとキャッシュはクリアされます。永続化には Pinia をご利用ください。
Q defineAsyncComponentとVue Routerの遅延読み込みの関係はどのようなものですか?
A Vue Routerは内部でdefineAsyncComponentを使用して、ルートの遅延読み込みを実装しています。本質的には、これらは同じものです。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

簡単なタブ切り替え機能を実装する:

(2) 上級問題(難易度:⭐⭐)

タブ機能と非同期読み込みの実装:

(3) チャレンジ問題(難易度:⭐⭐⭐)

完全な動的・非同期・キャッシュ機能を備えたシステムの実装:

  1. タブが5つあり、そのうち少なくとも2つは非同期で読み込まれる
  2. <Suspense>で包む
  3. <keep-alive :max="3"> キャッシュ
  4. 切り替え時に onActivated/onDeactivated を出力する
  5. 非同期コンポーネントが失敗した際に、ErrorComponent を表示する
  6. ファーストスクリーンの読み込み時間を測定する(非同期化の前後)
  7. TypeScript:強型付けの動的コンポーネント
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%