PreFetch 是一項功能 (僅在使用 Quasar CLI 時可用),允許 Vue Router 拾取的元件 (在 /src/router/routes.js
中定義) 以
- 預先提取資料
- 驗證路由
- 在不符合某些條件時 (例如使用者未登入) 重新導向到另一個路由
- 可以協助初始化 Store 狀態
以上所有操作都會在實際路由元件呈現之前執行。
它旨在與所有 Quasar 模式 (SPA、PWA、SSR、Cordova、Electron) 搭配使用,但對於 SSR 建置尤其有用。
安裝
return {
preFetch: true
}
警告
當您使用它來預先提取資料時,您可能想要使用 Pinia 或 Vuex,所以請確保您的專案資料夾在建立專案時,有 /src/stores
(適用於 Pinia) 或 /src/store
(適用於 Vuex) 資料夾,否則請產生一個新專案並將 store 資料夾內容複製到您目前的專案中 (或使用 quasar new store
命令)。
PreFetch 如何協助 SSR 模式
此功能對於 SSR 模式特別有用 (但不僅限於此)。在 SSR 期間,我們基本上是渲染應用程式的「快照」,因此如果應用程式依賴某些非同步資料,**那麼這些資料需要在我們開始渲染過程之前預先提取並解析**。
另一個考量是,在客戶端,相同的資料需要在我們掛載客戶端應用程式之前可用 - 否則客戶端應用程式將使用不同的狀態進行渲染,並且 hydration 將會失敗。
為了處理這個問題,提取的資料需要存在於視圖組件之外,在專用的資料儲存區或「狀態容器」中。在伺服器端,我們可以在渲染之前預先提取資料並將其填入儲存區。客戶端儲存區將在我們掛載應用程式之前直接取得伺服器狀態。
PreFetch 何時被啟用
preFetch
鉤子 (在接下來的章節中描述) 由訪問的路徑決定 - 這也決定了渲染哪些組件。實際上,給定路徑所需的資料也是在該路徑渲染的組件所需的資料。**因此,將鉤子邏輯僅放置在路由組件內是自然的 (也是必需的)。** 這包括 /src/App.vue
,在這種情況下,它只會在應用程式啟動時運行一次。
讓我們舉一個例子,以便了解何時調用鉤子。假設我們有這些路由,並且我們已為所有這些組件編寫了 preFetch
鉤子
[
{
path: '/',
component: LandingPage
},
{
path: '/shop',
component: ShopLayout,
children: [
{
path: 'all',
component: ShopAll
},
{
path: 'new',
component: ShopNew
},
{
path: 'product/:name',
component: ShopProduct,
children: [{
path: 'overview',
component: ShopProductOverview
}]
}
]
}
]
現在,讓我們看看當使用者按照下面指定的順序逐個訪問這些路由時,鉤子是如何被調用的。
正在訪問的路徑 | 從以下組件調用的鉤子 | 觀察 |
---|---|---|
/ | App.vue 然後 LandingPage | App.vue 鉤子被調用,因為我們的應用程式啟動了。 |
/shop/all | ShopLayout 然後 ShopAll | - |
/shop/new | ShopNew | ShopNew 是 ShopLayout 的子組件,並且 ShopLayout 已經渲染,因此 ShopLayout 不會再次被調用。 |
/shop/product/pyjamas | ShopProduct | - |
/shop/product/shoes | ShopProduct | Quasar 注意到相同的組件已經渲染,但是路由已經更新並且它有路由參數,所以它再次調用鉤子。 |
/shop/product/shoes/overview | ShopProduct 然後 ShopProductOverview | ShopProduct 有路由參數,所以即使它已經渲染,也會被調用。 |
/ | LandingPage | - |
用法
鉤子在我們的路由組件上被定義為一個名為 preFetch
的自訂靜態函數。請注意,由於此函數將在組件實例化之前被調用,因此它無法存取 this
。
以下範例是使用 Vuex 的情況
<template>
<div>{{ item.title }}</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
// our hook here
preFetch ({ store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath }) {
// fetch data, validate route and optionally redirect to some other route...
// ssrContext is available only server-side in SSR mode
// No access to "this" here
// Return a Promise if you are running an async job
// Example:
return store.dispatch('fetchItem', currentRoute.params.id)
},
setup () {
const $store = useStore()
// display the item from store state.
const item = computed(() => $store.state.items[this.$route.params.id])
return { item }
}
}
</script>
如果您正在使用 <script setup>
(和 Vue 3.3+)
<script setup>
/**
* The defineOptions is a macro.
* The options will be hoisted to module scope and cannot access local
* variables in <script setup> that are not literal constants.
*/
defineOptions({
preFetch () {
console.log('running preFetch')
}
})
</script>
提示
如果您正在開發 SSR 應用程式,那麼您可以查看伺服器端提供的 ssrContext 物件。
// related action for Promise example
// ...
actions: {
fetchItem ({ commit }, id) {
return axiosInstance.get(url, id).then(({ data }) => {
commit('mutation', data)
})
}
}
// ...
重定向範例
以下是在某些情況下重定向使用者的範例,例如當他們嘗試訪問只有經過身份驗證的使用者才能看到的頁面時。
// We assume here we already wrote the authentication logic
// in the Vuex Store, so take as a high-level example only.
preFetch ({ store, redirect }) {
if (!store.state.authenticated) {
redirect({ path: '/login' })
}
}
預設情況下,重定向會以狀態回應碼 302 發生,但是我們可以在調用函數時將此狀態碼作為第二個可選參數傳遞,就像這樣
redirect({ path: '/moved-permanently' }, 301)
如果調用了 redirect(false)
(僅在客戶端支援!),它會中止當前的路由導航。請注意,如果您在 src/App.vue
中像這樣使用它,它將會停止應用程式啟動,這是不可取的。
redirect()
方法需要一個 Vue Router location Object。
使用 preFetch 初始化 Pinia 或 Vuex Store
preFetch
鉤子僅在應用程式啟動時運行一次,因此您可以使用這個機會在此處初始化 Pinia store 或 Vuex Store。
// App.vue - handling Pinia stores
// example with a store named "myStore"
// placed in /src/stores/myStore.js|ts
import { useMyStore } from 'stores/myStore'
export default {
// ...
preFetch () {
const myStore = useMyStore()
// do something with myStore
}
}
export default {
// ...
preFetch ({ store }) {
// initialize something in store here
}
}
Vuex Store 代碼分割
在大型應用程式中,您的 Vuex store 很可能會被拆分為多個模組。當然,也可以將這些模組代碼分割成相應的路由組件塊。假設我們有以下 store 模組
// we've merged everything into one file here;
// an initialized Quasar project splits every component of a Vuex module
// into separate files, but for the sake of the example
// here in the docs, we show this module as a single file
export default {
namespaced: true,
// IMPORTANT: state must be a function so the module can be
// instantiated multiple times
state: () => ({
count: 0
}),
actions: {
inc: ({ commit }) => commit('inc')
},
mutations: {
inc: state => state.count++
}
}
現在,我們可以使用 store.registerModule()
在路由組件的 preFetch()
鉤子中延遲註冊此模組
<template>
<div>{{ fooCount }}</div>
</template>
<script>
import { useStore } from 'vuex'
import { onMounted, onUnmounted } from 'vue'
// import the module here instead of in `src/store/index.js`
import fooStoreModule from 'store/foo'
export default {
preFetch ({ store }) {
store.registerModule('foo', fooStoreModule)
return store.dispatch('foo/inc')
},
setup () {
const $store = useStore()
onMounted(() => {
// Preserve the previous state if it was injected from the server
$store.registerModule('foo', fooStoreModule, { preserveState: true })
})
onUnmounted(() => {
// IMPORTANT: avoid duplicate module registration on the client
// when the route is visited multiple times.
$store.unregisterModule('foo')
})
const fooCount = computed(() => {
return $store.state.foo.count
})
return {
fooCount
}
}
}
</script>
另請注意,由於模組現在是路由組件的依賴項,因此它將被 Vite 移動到路由組件的異步 chunk 中。
警告
別忘了為 registerModule
使用 preserveState: true
選項,以便我們保留伺服器注入的狀態。
搭配 Vuex 和 TypeScript 使用
您可以使用 preFetch
helper 來類型提示 store 參數 (否則它將具有 any
類型)
import { preFetch } from 'quasar/wrappers'
import { Store } from 'vuex'
interface StateInterface {
// ...
}
export default {
preFetch: preFetch<StateInterface>(({ store }) => {
// Do something with your newly-typed store parameter
}),
}
提示
這僅適用於類型化 store
參數,即使使用正常語法,其他參數也會自動類型化。
載入狀態
良好的 UX 包括在使用者等待頁面準備就緒時,通知使用者後台正在處理某些事情。Quasar CLI 為此提供了兩種開箱即用的選項。
載入指示器 (LoadingBar)
當您將 Quasar LoadingBar 外掛程式添加到您的應用程式時,Quasar CLI 將在預設情況下在運行 preFetch 鉤子時使用它。
載入 (Loading)
也可以使用 Quasar Loading 外掛程式。這是一個範例
import { Loading } from 'quasar'
export default {
// ...
preFetch ({ /* ... */ }) {
Loading.show()
return new Promise(resolve => {
// do something async here
// then call "resolve()"
}).then(() => {
Loading.hide()
})
}
}