Vue3 - 懶加載與非同步組件
tags: Vue
category: Front-End
description: Vue3 - 懶加載(lazy loading)與非同步組件
created_at: 2022/07/23 20:00:00
回到 手把手開始寫 Vue3
前言
這兩個雖然不太一樣,但反正都跟組件有關就放一起了。
一個是讓頁面跑出來的速度快一些,讓部分組件需要的時候再去發 request
而不是一開始就都送。
一個是組件本身的行為是非同步(async
)的,例如有發 fetch
之類的,那麼在 pending
的過程可能需要做一些 loading
畫面在用的。
懶加載?
其實我也不是很確定這個中文是不是這樣,不過主要就是在組件需要的時候才去發 request
,而不是在第一時間就全部發。
直接上範例
<script setup>
import { ref } from "vue";
import LoadingComponent from "./components/LoadingComponent.vue";
const show = ref(false);
</script>
<template>
<button class="px-4 py-2 bg-blue-500 text-white" @click="show = !show">
Switch
</button>
<LoadingComponent v-if="show"></LoadingComponent>
</template>
而那個 LoadingComponent
可以替換成任何你想要的東西。
目前這樣的執行結果是沒有懶加載的,所以即使一開始並沒有呈現出來,但還是發出了 request
。
而要加入懶加載就要使用一個 defineAsyncComponent
的函數,基本使用方式如下:
<script setup>
import { ref, defineAsyncComponent } from "vue";
const LoadingComponent = defineAsyncComponent(() =>
import("./components/LoadingComponent.vue")
);
const show = ref(false);
</script>
<template>
<button class="px-4 py-2 bg-blue-500 text-white" @click="show = !show">
Switch
</button>
<LoadingComponent v-if="show"></LoadingComponent>
</template>
這時候你會發現一開始並不會去發 LoadingComponent
的 request
,而是在你點下按鈕要他顯示的時候才會去送。
而這個函數也有一些 options
可以下,如下(官方範例):
const AsyncComp = defineAsyncComponent({
// loader function,要回傳一個 promise
// resolve component (若有等待會顯示 loadingComponent)
// 或
// reject 掉,會進入 errorComponent
loader: () => import('./Foo.vue'),
// 就 loadingComponent
loadingComponent: LoadingComponent,
// 等待多久在顯示loadingComponent. 預設是200ms.
delay: 200,
// 就 errorComponent
errorComponent: ErrorComponent,
// 超時的時間. 預設是不會超時.
timeout: 3000
})
用下面這種設定去玩,應該可以玩出一些心得:
const AComponent = defineAsyncComponent({
loader: () =>
new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve(import("./components/A.vue"));
// }, 4000);
setTimeout(() => {
reject("hi");
}, 1500);
}),
loadingComponent: LoadingComponent,
delay: 1000,
errorComponent: ErrorComponent,
timeout: 3000,
});
可以切換 setTimeout
的區塊,或是修改時間。
然後會發現那個 loader
可以直接帶 import()
,這是因為實際上 import()
他所回傳的也是一個 promise
,而裡面的值就是那個模組的東西,可以看ES module dynamic import
處理非同步組件
先做一個假設,有一個組件包含 setTimeout
,假設他是去打 API
拿回資料。
src/components/AsyncChild.vue
<script setup>
import { ref } from "vue";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const data = ref([]);
sleep(2000).then(() => {
data.value = [
{
id: 1,
value: 1,
},
{
id: 2,
value: 2,
},
{
id: 3,
value: 3,
},
];
});
</script>
<template>hi {{ data }}</template>
src/App.vue
<script setup>
import AsyncChild from "./components/AsyncChild.vue";
</script>
<template>
<AsyncChild />
</template>
這時候你會看到2秒後呈現出 data
的內容,然後因為還沒做 loading
的處理,所以會有點乾。
假設稍微簡單做一下:
<script setup>
import { ref } from "vue";
import LoadingComponent from "./LoadingComponent.vue";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const data = ref(null);
sleep(2000).then(() => {
data.value = [
{
id: 1,
value: 1,
},
{
id: 2,
value: 2,
},
{
id: 3,
value: 3,
},
];
});
</script>
<template>
<div v-if="data">hi {{ data }}</div>
<LoadingComponent v-else />
</template>
那可想而知,如果我有 10
個這樣的組件,這樣的行為大概就要做 10
次。
那麼這時可以使用 <Suspense>
,但是官方有寫說目前這還是屬於實驗性質的 API
,可能還不穩定。
大概會長成這樣
<script setup>
import AsyncChild from "./components/AsyncChild.vue";
import LoadingComponent from "./components/LoadingComponent.vue";
</script>
<template>
<Suspense>
<AsyncChild />
<template #fallback>
<LoadingComponent />
</template>
</Suspense>
</template>
然後這時的 AsyncChild.vue
,主要要修改的是 async
相關的程式,讓回傳組件變成一個 promise
await sleep(2000).then(() => {
// ...
});
在頂層 <script setup></script>
語法糖之中是可以直接用 await
的,這時就會回傳一個 promise
這樣再去看就會先進入 2
秒的 Loading
在顯示 AsyncChild
的內容了。
而在這時應該也注意到了,在 VSCode
當中他會噴 ESLint
的錯誤 (await
)
Parsing error: Cannot use keyword 'await' outside an async functioneslint
解決方案在這: https://eslint.vuejs.org/user-guide/#parsing-error-with-top-level-await
只要根據對應版本加入一些設定就可以了。
事件
使用這個 <Suspense>
還可以監聽一些事件,分別是
pending
resolve
fallback
可以像這樣子玩玩:
<script setup>
import AsyncChild from "./components/AsyncChild.vue";
import LoadingComponent from "./components/LoadingComponent.vue";
const onPending = () => {
console.log("onPending");
};
const onResolve = () => {
console.log("onResolve");
};
const onFallback = () => {
console.log("onFallback");
};
</script>
<template>
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
<AsyncChild />
<template #fallback>
<LoadingComponent />
</template>
</Suspense>
</template>
這三個事件應該很好理解。