EP28 - 非同步組件

閱讀時間約 20 分鐘
Async Components 非同步我是知道這個東西!
是讓加載組件的時候可以非同步,改善用戶體驗吧~?
這裡應該不包含資料的非同步加載吧!?~

基本用法 - Basic Usage

在大型應用中,我們可能需要將應用劃分為較小的部分,並且僅在需要時從伺服器加載組件。為了實現這一點,Vue 提供了 defineAsyncComponent 函數:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...從伺服器加載組件
resolve(/* 加載的組件 */)
})
})
// ...像正常組件一樣使用 `AsyncComp`

如你所見,defineAsyncComponent 接受一個加載函數,該函數返回一個 Promise。當你從伺服器獲取到組件定義時,應該調用 Promise 的 resolve 回調。如果加載失敗,也可以調用 reject(reason) 來指示加載失敗。

ES 模塊的動態導入也會返回一個 Promise,因此大多數情況下,我們會將它與 defineAsyncComponent 結合使用。像 Vite 和 webpack 這樣的打包工具也支持該語法(並會將其作為捆綁分割點),因此我們可以用它來導入 Vue 單文件組件(SFC):

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)

結果的 AsyncComp 是一個包裝組件,只有在實際渲染在頁面上時才會調用加載函數。此外,它會將任何屬性和插槽傳遞給內部組件,因此你可以使用這個非同步包裝器無縫地替換原始組件,同時實現延遲加載。

與普通組件一樣,可以使用 app.component() 全局註冊非同步組件:

app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))

也可以直接在其父組件內部定義:

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

const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>

<template>
<AdminPage />
</template>

Q: 什麼是Promise?

Promise 是 JavaScript 中的一個對象,用於表示一個異步操作的最終完成(或失敗)及其結果值。簡單來說,Promise 可以讓你在執行異步操作時更方便地處理結果,尤其是在處理異步請求時(如 AJAX 請求)。

Promise 的狀態

Promise 具有三種狀態:

  1. 待定(Pending):初始狀態,既不是成功也不是失敗。
  2. 已解決(Fulfilled):異步操作成功完成,並返回了一個結果。
  3. 已拒絕(Rejected):異步操作失敗,並返回了一個錯誤原因。

使用 Promise 的基本範例

以下是一個使用 Promise 的簡單範例:

const myPromise = new Promise((resolve, reject) => {
// 假設這是一個異步操作,例如 AJAX 請求
const success = true; // 模擬操作成功或失敗的標誌

if (success) {
resolve("操作成功!"); // 異步操作成功,調用 resolve
} else {
reject("操作失敗!"); // 異步操作失敗,調用 reject
}
});

// 使用 then() 處理已解決的狀態,catch() 處理已拒絕的狀態
myPromise
.then(result => {
console.log(result); // 輸出: 操作成功!
})
.catch(error => {
console.error(error); // 如果失敗,輸出錯誤原因
});

Promise 的鏈式調用

Promise 允許鏈式調用,即在一個 Promise 完成後可以再返回一個新的 Promise,這樣可以方便地處理多個異步操作。例如:

fetch('https://api.example.com/data')
.then(response => response.json()) // 將 response 轉換為 JSON
.then(data => {
console.log(data); // 使用獲取到的數據
})
.catch(error => {
console.error('發生錯誤:', error); // 處理錯誤
});

這段程式碼實際上是使用 fetch API,它本身就會返回一個 Promise 物件。這裡是 Promise 的運作方式的具體說明:

fetch 函數

fetch 函數用於發送網絡請求,它會返回一個 Promise。這個 Promise 代表請求的狀態,會在請求完成後進行解決(resolve)或拒絕(reject)。

總結

Promise 使得處理異步操作更加簡潔和可讀,尤其是在需要執行多個連續的異步操作時。使用 Promise 可以避免回調地獄(callback hell)現象,使代碼更易於維護和理解。

加載和錯誤狀態 - Loading and Error States

非同步操作必然涉及加載和錯誤狀態——defineAsyncComponent() 支持通過高級選項處理這些狀態:

const AsyncComp = defineAsyncComponent({
// 加載函數
loader: () => import('./Foo.vue'),

// 加載非同步組件時使用的組件
loadingComponent: LoadingComponent,
// 顯示加載組件的延遲。默認:200ms。
delay: 200,

// 加載失敗時使用的組件
errorComponent: ErrorComponent,
// 如果提供並超過超時,將顯示錯誤組件。默認:Infinity。
timeout: 3000
})

如果提供了加載組件,則在內部組件加載時會首先顯示它。顯示加載組件之前會有一個默認的 200 毫秒延遲,這是因為在快速網絡下,瞬時的加載狀態可能會被替換得太快,最終看起來像是閃爍。

如果提供了錯誤組件,當加載函數返回的 Promise 被拒絕時,它會顯示。如果請求花費的時間過長,你還可以指定超時,以顯示錯誤組件。

Q: 什麼是Hydration 水合?

水合(Hydration) 是一個在伺服器端渲染(Server-Side Rendering, SSR)應用程式中使用的概念,特別是在像 Vue.js 和 React 這樣的框架中。這個過程涉及將伺服器生成的靜態 HTML 轉換為可互動的客戶端應用程式。

水合的工作流程

  1. 伺服器端渲染:當用戶請求一個頁面時,伺服器生成一個完整的 HTML 頁面並發送給用戶的瀏覽器。這樣用戶可以快速看到內容,而不必等待 JavaScript 完全加載。
  2. 客戶端加載 JavaScript:當瀏覽器接收到 HTML 時,JavaScript 檔案也會被加載。這些 JavaScript 代碼包含了應用程式的邏輯和行為。
  3. 添加互動性:在 JavaScript 加載完成後,框架會將伺服器生成的靜態內容「水合」成可互動的元素。這包括將事件處理器綁定到 DOM 元素,使得用戶可以與應用程式互動。

水合的好處

  • 快速響應時間:用戶可以立即看到頁面內容,而不必等待所有 JavaScript 完全加載。這提高了首次渲染的速度。
  • SEO 友好:伺服器端渲染的頁面對搜尋引擎更友好,因為搜尋引擎可以抓取到靜態 HTML 內容,而不是等待 JavaScript 加載。
  • 增強用戶體驗:用戶在等待 JavaScript 加載時,仍然能夠看到和訪問內容,提供更流暢的體驗。

水合的挑戰

  • 狀態不一致:如果伺服器端渲染的 HTML 和客戶端的 JavaScript 之間存在狀態不一致,可能會導致重水合過程中的錯誤或不正確的顯示。
  • 性能開銷:水合過程會消耗資源,特別是在需要大量 DOM 操作的情況下。

延遲水合 - Lazy Hydration (3.5+)

這部分僅適用於使用伺服器端渲染的情況。

在 Vue 3.5+ 中,非同步組件可以通過提供水合策略來控制何時水合。

Vue 提供了多種內建水合策略。這些內建策略需要單獨導入,以便在未使用時進行樹搖(tree-shaking)。

設計上是故意低層次的,以提高靈活性。將來可以在核心或更高層次的解決方案(例如 Nuxt)上構建編譯器語法糖。

空閒時水合 - Hydrate on Idle

通過 requestIdleCallback 進行水合:

import { defineAsyncComponent, hydrateOnIdle } from 'vue'

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnIdle(/* 可選地傳入最大超時 */)
})

可見時水合 - Hydrate on Visible

當元素變得可見時通過 IntersectionObserver 進行水合。

import { defineAsyncComponent, hydrateOnVisible } from 'vue'

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible()
})

可以選擇性地傳入觀察者的選項對象值:

hydrateOnVisible({ rootMargin: '100px' })

媒體查詢時水合 - Hydrate on Media Query

當指定的媒體查詢匹配時進行水合。

import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnMediaQuery('(max-width:500px)')
})

交互時水合 - Hydrate on Interaction

當在組件元素上觸發指定事件時進行水合。觸發水合的事件也會在水合完成後重放。

import { defineAsyncComponent, hydrateOnInteraction } from 'vue'

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnInteraction('click')
})

也可以是多個事件類型的列表:

hydrateOnInteraction(['wheel', 'mouseover'])

自定義策略 - Custom Strategy

import { defineAsyncComponent, type HydrationStrategy } from 'vue'

const myStrategy: HydrationStrategy = (hydrate, forEachElement) => {
// forEachElement 是一個幫助函數,用於迭代組件的非水合 DOM 中的所有根元素,
// 因為根元素可以是片段而不是單一元素
forEachElement(el => {
// ...
})
// 準備好後調用 `hydrate`
hydrate()
return () => {
// 如果需要,返回一個清理函數
}
}

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: myStrategy
})

與 Suspense 一起使用 - Using with Suspense

非同步組件可以與 <Suspense> 內建組件一起使用。<Suspense> 和非同步組件之間的交互已在專門的 <Suspense> 章節中記錄。

Q: 什麼是Suspense?

Suspense 是 Vue 3 中引入的一個功能,用於處理異步組件的加載狀態。它允許開發者更輕鬆地管理異步組件在加載過程中的顯示效果,例如在組件尚未加載完成時顯示一個加載指示器或佔位符。

Suspense 的工作原理

  1. 包裹異步組件:將一個或多個異步組件包裹在 <Suspense> 組件中,這樣可以控制當這些異步組件加載時的顯示內容。
  2. 使用 slot<Suspense> 提供了兩個 slot:
  • default slot:用於顯示加載完成後的內容。
  • fallback slot:用於顯示在加載期間的內容,例如加載指示器或佔位符。

以下是一個使用 Suspense 的範例:

<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<LoadingSpinner /> <!-- 加載時顯示的內容 -->
</template>
</Suspense>
</template>

<script setup>
import AsyncComponent from './AsyncComponent.vue'
import LoadingSpinner from './LoadingSpinner.vue'
</script>

在這個例子中,當 AsyncComponent 正在加載時,<LoadingSpinner /> 將顯示在畫面上。一旦 AsyncComponent 加載完成,<AsyncComponent /> 將取代加載指示器顯示。

使用場景

  • 異步數據加載:當需要從伺服器獲取數據並且這個過程可能需要一些時間時,可以使用 Suspense 來提供良好的用戶體驗。
  • 多個異步組件:當有多個異步組件需要加載時,可以使用 Suspense 來統一管理它們的加載狀態。

結論

Suspense 是 Vue 3 提供的一個強大功能,旨在簡化異步組件的管理和顯示。它可以提升用戶體驗,讓開發者更方便地處理異步加載時的狀態和顯示效果。

Q: 為什麼要用SSR?

使用 伺服器端渲染(SSR, Server-Side Rendering) 有多個優點,特別是在構建現代化的網頁應用程式時。以下是一些使用 SSR 的原因:

1. 更快的初始加載時間

  • SSR 會在伺服器上預渲染 HTML,這樣用戶在首次加載頁面時可以立即看到內容,而不需要等待 JavaScript 加載和執行完成。這對於用戶體驗尤為重要,特別是在網速較慢的情況下。

2. 更好的 SEO(搜索引擎優化)

  • 搜索引擎的爬蟲在索引網頁內容時通常對 JavaScript 渲染的內容支持不佳。使用 SSR,頁面的 HTML 在伺服器端生成,搜索引擎可以輕易讀取並索引這些內容,從而提高網站的可見性和排名。

3. 改善社交媒體分享

  • 在 SSR 中生成的 HTML 包含完整的內容,這樣當用戶在社交媒體上分享頁面鏈接時,社交平台能夠抓取到正確的預覽信息(例如標題、描述、縮略圖等),提高了分享的效果。

4. 降低客戶端負擔

  • SSR 將大部分渲染工作移至伺服器端,這可以降低客戶端設備(特別是移動設備)的運算負擔,提供更流暢的用戶體驗。

5. 更佳的性能優化

  • SSR 使得在伺服器端進行性能優化變得更加容易,例如使用快取來提高響應速度,這對於需要快速響應的應用特別重要。

6. 支持多種渲染策略

  • 使用 SSR 可以更靈活地支持不同的渲染策略,例如先渲染主要內容,然後再進行其他次要內容的加載。

7. 可擴展性

  • SSR 可以輕鬆地與其他技術堆棧結合使用,例如與 Node.js、Express、Django 等後端框架協作,提供更完整的全棧解決方案。

總結

SSR 提供了更好的性能、SEO 和用戶體驗,使其成為構建現代網頁應用程式的一個非常有效的解決方案。然而,實現 SSR 也需要考慮一些挑戰,例如伺服器負載和複雜的開發過程,因此在選擇使用 SSR 時需要根據具體需求進行評估。

這章節有些JS沒學好的觀念一起補齊,
這樣才能對框架的非同步能有更深的理解www~
文章中很多Q的話~就是我個人的疑問,
盡量找好理解的資訊放上去了,難道大家都這麼清楚嗎?
Vue + Nuxt.js = SSR









avatar-img
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
從 3.4 開始,推薦使用 defineModel() 巨集來自動化這個過程,讓寫法更加簡潔,支持多個 v-model 綁定,並可直接使用自定義修飾符。新方法還能同步父子組件的值,減少同步問題,使組件的開發與維護變得更方便
子組件可以使用 $emit 方法來發送自定義事件,父組件則使用 v-on 來監聽這些事件。事件參數可以通過 $emit 傳遞,並在父組件中使用內聯箭頭函數或方法接收。使用 defineEmits 可以顯式宣告事件並進行驗證,提升代碼可讀性和可維護性。
props的值可以是字符串數組或物件語法,每個屬性的鍵是prop的名字,值是預期類型的建構函數。當解構props時,Vue會自動添加props.前綴以保持響應性。Prop驗證確保了數據類型和要求的一致性,使開發更嚴謹。
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
從 3.4 開始,推薦使用 defineModel() 巨集來自動化這個過程,讓寫法更加簡潔,支持多個 v-model 綁定,並可直接使用自定義修飾符。新方法還能同步父子組件的值,減少同步問題,使組件的開發與維護變得更方便
子組件可以使用 $emit 方法來發送自定義事件,父組件則使用 v-on 來監聽這些事件。事件參數可以通過 $emit 傳遞,並在父組件中使用內聯箭頭函數或方法接收。使用 defineEmits 可以顯式宣告事件並進行驗證,提升代碼可讀性和可維護性。
props的值可以是字符串數組或物件語法,每個屬性的鍵是prop的名字,值是預期類型的建構函數。當解構props時,Vue會自動添加props.前綴以保持響應性。Prop驗證確保了數據類型和要求的一致性,使開發更嚴謹。
你可能也想看
Google News 追蹤
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
※ 補充說明: ※ npm 常用指令: ◦ npm init–y:快速初始化一個新的 Node.js 並建立一個 package.json 文件的命令。 ◦ npm info 套件名稱 version:快速查詢指定 npm 套件的最新版本號。 ◦ npm install套件名稱:用來安裝
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
套件(Package)是將程式或程式庫進行組織、分發和共享的一種方式。在軟體開發中,套件通常包含了相關的程式碼、資源文件和元數據,並提供了統一的名稱空間和版本管理。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。
Thumbnail
為什麼需要非同步? 我們在「【Web微知識系列】 Web Workers」有介紹到在瀏覽器可執行腳本Javascript環境底下如何完成非同步的操作, 主要是為了讓任務更有效率的進行, 不會因為一個非常耗時的工作堵塞住整個服務, 導致無法服務他人的窘境。 大家應該經常在餐廳裡會看到服務員協
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
※ 靜態資源回傳 ※ 什麼是靜態資源: 定義:是指事先準備好的資源,這些資源在伺服器上是靜態的、不會隨著每個請求而改變。 資源通常包括: 靜態 HTML 文件。 CSS。 圖像(Image)。 Video。 字體文件:google fonts。 favicon:網頁名稱旁邊的ico
※ 補充說明: ※ npm 常用指令: ◦ npm init–y:快速初始化一個新的 Node.js 並建立一個 package.json 文件的命令。 ◦ npm info 套件名稱 version:快速查詢指定 npm 套件的最新版本號。 ◦ npm install套件名稱:用來安裝
Thumbnail
在 TypeScript 中,套件是模組化代碼的集合,可以提高代碼的可重用性和可維護性。常見的套件包括各種庫和框架,如 lodash、express 等。以下是有關引用套件、自定義套件和常見套件的詳細介紹。
Thumbnail
需求情境: 在設計畫面時,資料來源是後台的 api,每一次畫面細節的修修改改,都會觸發 Xcode Preview 程序,導致不斷呼叫後台。此時若資料結構和大小都具有一定規模,就會導致效率低落,不斷等待,且消耗伺服器資源甚鉅。 解決方案: 將後台傳回的資料以檔案形式暫存在本地端,每次 pr
Thumbnail
套件(Package)是將程式或程式庫進行組織、分發和共享的一種方式。在軟體開發中,套件通常包含了相關的程式碼、資源文件和元數據,並提供了統一的名稱空間和版本管理。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。
Thumbnail
為什麼需要非同步? 我們在「【Web微知識系列】 Web Workers」有介紹到在瀏覽器可執行腳本Javascript環境底下如何完成非同步的操作, 主要是為了讓任務更有效率的進行, 不會因為一個非常耗時的工作堵塞住整個服務, 導致無法服務他人的窘境。 大家應該經常在餐廳裡會看到服務員協