為何捐款
API 瀏覽器
升級指南 (Upgrade guide)
NEW!
quasar.config 檔案 (The quasar.config file)
將專案轉換為搭配 Vite 的 CLI (Convert project to CLI with Vite)
瀏覽器相容性 (Browser Compatibility)
支援 TypeScript (Supporting TypeScript)
目錄結構 (Directory Structure)
命令列表 (Commands List)
CSS 預處理器 (CSS Preprocessors)
路由 (Routing)
延遲載入 - 代碼分割 (Lazy Loading - Code Splitting)
資源處理 (Handling Assets)
啟動檔案 (Boot Files)
預取功能 (Prefetch Feature)
API 代理 (API Proxying)
Vite 處理 (Handling Vite)
process.env 處理 (Handling process.env)
使用 Pinia 的狀態管理 (State Management with Pinia)
使用 Vuex 的狀態管理 (State Management with Vuex)
Linter
測試 & 稽核 (Testing & Auditing)
開發行動應用程式
Ajax 請求
開放開發伺服器給公眾
Quasar CLI 搭配 Vite - @quasar/app-vite
PreFetch 功能

PreFetch 是一項功能 (僅在使用 Quasar CLI 時可用),允許 Vue Router 拾取的元件 (在 /src/router/routes.js 中定義) 以

  • 預先提取資料
  • 驗證路由
  • 在不符合某些條件時 (例如使用者未登入) 重新導向到另一個路由
  • 可以協助初始化 Store 狀態

以上所有操作都會在實際路由元件呈現之前執行。

它旨在與所有 Quasar 模式 (SPA、PWA、SSR、Cordova、Electron) 搭配使用,但對於 SSR 建置尤其有用。

安裝

/quasar.config 檔案

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 然後 LandingPageApp.vue 鉤子被調用,因為我們的應用程式啟動了。
/shop/allShopLayout 然後 ShopAll-
/shop/newShopNewShopNew 是 ShopLayout 的子組件,並且 ShopLayout 已經渲染,因此 ShopLayout 不會再次被調用。
/shop/product/pyjamasShopProduct-
/shop/product/shoesShopProductQuasar 注意到相同的組件已經渲染,但是路由已經更新並且它有路由參數,所以它再次調用鉤子。
/shop/product/shoes/overviewShopProduct 然後 ShopProductOverviewShopProduct 有路由參數,所以即使它已經渲染,也會被調用。
/LandingPage-

用法

鉤子在我們的路由組件上被定義為一個名為 preFetch 的自訂靜態函數。請注意,由於此函數將在組件實例化之前被調用,因此它無法存取 this

以下範例是使用 Vuex 的情況

某些用作路由的 .vue 組件

<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
  }
}
App.vue - 處理 Vuex store

export default {
  // ...
  preFetch ({ store }) {
    // initialize something in store here
  }
}

Vuex Store 代碼分割

在大型應用程式中,您的 Vuex store 很可能會被拆分為多個模組。當然,也可以將這些模組代碼分割成相應的路由組件塊。假設我們有以下 store 模組

/src/store/foo.js

// 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 外掛程式。這是一個範例

一個路由 .vue 組件

import { Loading } from 'quasar'

export default {
  // ...
  preFetch ({ /* ... */ }) {
    Loading.show()

    return new Promise(resolve => {
      // do something async here
      // then call "resolve()"
    }).then(() => {
      Loading.hide()
    })
  }
}