はじめに
Vue.jsの勉強をしていて、defineAsyncComponentというものに出会ったので、学んだことをまとめました。
非同期コンポーネントとは
非同期コンポーネントとは、コンポーネントを必要なときに動的にロードする機能です。
画面に表示されていないときなどの不要なときにコンポーネントの読み込みが発生しないため、初期ロード時などの場面ではパフォーマンスの向上が期待できます。
VueではdefineAsyncComponent関数を使用して、非同期コンポーネントを実装します。
defineAsyncComponentの基本的な使い方
例としてWebサイトでよく目にする、Step1からStep2へ遷移するような場面で挙動を確認していきます。
また本記事のサンプルコードは、スタイル指定にSCSS記法を採用しています。(SCSS記法を使用するには別途設定が必要です。)
まずは非同期コンポーネントを使用しない場合です。
ボタンがクリックされるとSTEPが切り替わるようにしています。
defineAsyncComponentを使用しない場合
【サンプルコード】
▼App.vue
<template>
<div :class="$style.wrapper">
<button :class="$style.button" @click="isShow = !isShow">
STEP{{ isShow ? "1" : "2" }}へ
</button>
<Step1 v-if="!isShow" />
<Step2 v-if="isShow" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Step1 from "./components/Step1.vue";
import Step2 from "./components/Step2.vue";
const isShow = ref(false);
</script>
<style lang="scss" module>
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding-block: 30px;
}
.button {
width: 200px;
height: 40px;
font-size: x-large;
}
</style>
▼Step1.vue
<template>
<div :class="$style.wrapper">
<h1 :class="$style.title">STEP1</h1>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" module>
.wrapper {
display: flex;
align-items: center;
width: 80%;
height: 500px;
margin: auto;
background-color: skyblue;
.title {
flex: 1;
font-size: 5rem;
text-align: center;
}
}
</style>
▼Step2.vue
<template>
<div :class="$style.wrapper">
<h1 :class="$style.title">STEP2</h1>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" module>
.wrapper {
display: flex;
align-items: center;
width: 80%;
height: 500px;
margin: auto;
background-color: yellowgreen;
.title {
flex: 1;
font-size: 5rem;
text-align: center;
}
}
</style>
【結果】
初期表示時にSTEP2は表示されないのですが、ブラウザの検証ツールのネットワークタブを確認すると、STEP2のコンポーネントも読み込まれていることがわかります。
サンプルのように2つのステップしかなく、中身が文字だけであればパフォーマンスへの影響はほとんどありませんが、ステップ数や中身が多くて複雑になればパフォーマンスに影響が出てくるでしょう。
次にdefineAsyncComponentを使用した場合を見てみます。
Step2コンポーネントを初期ロード時に読み込まないようにします。
以下がdefineAsyncComponentの最もシンプルな使用方法ですので、こちらを利用して実装してみます。
const AsyncComponent = defineAsyncComponent(
() => import("./components/Step2.vue")
);
実際に先ほどのサンプルコードに落とし込んでみます。
defineAsyncComponentを使用した場合
【サンプルコード】
▼App.vue
<template>
<div :class="$style.wrapper">
<button :class="$style.button" @click="isShow = !isShow">
STEP{{ isShow ? "1" : "2" }}へ
</button>
<!-- この部分 -->
<Step1 v-if="!isShow" />
<AsyncComponent v-if="isShow" />
</div>
</template>
<script setup lang="ts">
import { ref, defineAsyncComponent } from "vue";
import Step1 from "./components/Step1.vue";
const isShow = ref(false);
// この部分
const AsyncComponent = defineAsyncComponent(
() =>
import("./components/Step2.vue")
);
</script>
<style lang="scss" module>
// 省略
</style>
【結果】
先ほどと同じようにネットワークを確認してみると、初期ロード時にStep2コンポーネントが読み込まれていないことがわかります。
今回はボタンをクリックして初めてStep2コンポーネントが読み込まれます。
※補足
以下のようにしてpropsやslotを渡すことも可能です。
propsとしてtitleを渡す場合
<AsyncComponent v-if="isShow" title="Step2のタイトル" />
slotを渡す場合
<AsyncComponent v-if="isShow" title="Step2のタイトル">
<div>コンテンツ</div>
</AsyncComponent>
ローディングとエラーの状態を設定する
少し応用的な使い方になるのですが、非同期コンポーネントの読み込み中や、読み込みに失敗した際に表示するコンポーネントを設定することもできます。
今回はボタンをクリックするとモーダルが表示される場面で挙動を確認していきます。
defineAsyncComponentを使用しない場合
【サンプルコード】
▼App.vue
<template>
<div :class="$style.wrapper">
<button :class="$style.button" @click="isShow = !isShow">ブログ詳細</button>
<template v-if="isShow">
<BlogModal title="非同期コンポーネント" />
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import BlogModal from "./components/BlogModal.vue";
const isShow = ref(false);
</script>
<style lang="scss" module>
.wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding-block: 30px;
.button {
width: 200px;
height: 40px;
font-size: x-large;
}
}
</style>
▼BlockModal.vue
<template>
<div :class="$style.wrapper">
<div :class="$style.blog">
<h1 :class="$style.title">{{ props.title }}</h1>
<img :class="$style.thumbnail" src="./site-logo_sky.svg" />
<div :class="$style.body">
<p>ここからがブログの本文です。</p>
<p v-for="n in 10" :key="n">
{{
n
}}行目。ブログの本文です。ブログの本文です。ブログの本文です。ブログの本文です。
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
title: string;
}
const props = withDefaults(defineProps<Props>(), {
title: "タイトルです。",
});
</script>
<style lang="scss" module>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
.blog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80%;
padding: 10px;
gap: 10px;
background-color: white;
.title {
width: 100%;
text-align: center;
font-size: 2rem;
font-weight: 800;
background-color: aliceblue;
}
.thumbnail {
width: 100%;
}
.body {
width: 100%;
padding: 10px;
background-color: aliceblue;
box-sizing: border-box;
}
}
}
</style>
【結果】
先ほどのStep遷移の例と同じように、defineAsyncComponentを使用しない場合は、表示されていないコンポーネントも読み込まれていることがわかります。
defineAsyncComponentを使用した場合
【サンプルコード】
▼App.vue
<template>
<div :class="$style.wrapper">
<button :class="$style.button" @click="isShow = !isShow">ブログ詳細</button>
<template v-if="isShow">
<!-- この部分 -->
<AsyncBlogModal title="非同期コンポーネント" />
</template>
</div>
</template>
<script setup lang="ts">
import { ref, defineAsyncComponent } from "vue";
import Loading from "./components/Loading.vue";
import Error from "./components/Error.vue";
const isShow = ref(false);
// この部分
const AsyncBlogModal = defineAsyncComponent({
loader: () => import("./components/BlogModal.vue"),
loadingComponent: Loading,
errorComponent: Error,
delay: 2000,
timeout: 5000,
});
</script>
<style lang="scss" module>
// 省略
</style>
【結果】
初期ロード時にモーダル用のコンポーネントが読み込まれていないことがわかります。
ボタンをクリックして初めてモーダル用のコンポーネントが読み込まれています。
今回のサンプルコードは、Step遷移の時とは違い少し行数が多いため、各行の説明をさせていただきます。
ローダー関数
非同期コンポーネントを読み込むための関数
loader: () => import("./components/BlogModal.vue")
以下のように処理を追加することも可能です。
loader: () => {
console.log("処理を追加できる");
import("./components/BlogModal.vue")
}
ローディングコンポーネント
非同期コンポーネントの読み込み中に表示するコンポーネント
loadingComponent: Loading
遅延
ローディングコンポーネントを表示する前の遅延時間(ミリ秒)
delay: 2000
エラーコンポーネント
読み込みに失敗した場合に表示するコンポーネント
errorComponent: Error
タイムアウト
エラーコンポーネントを表示するまでの時間(ミリ秒)
timeout: 5000
エラーコンポーネントの表示を確認するために、delayに設定した値よりも短い時間をtimeoutに設定して意図的にエラーを発生させてキャプチャを取っています。