為何捐款
API 瀏覽器
升級指南
NEW!
quasar.config 檔案
轉換為搭配 Webpack 的 CLI
瀏覽器相容性
支援 TypeScript
目錄結構
指令列表
CSS 預處理器
路由
延遲載入 - 程式碼分割
處理資源
啟動檔案
預先載入功能
API 代理
處理 Webpack
處理 process.env
使用 Pinia 的狀態管理
使用 Vuex 的狀態管理
Linter
測試與稽核
開發行動應用程式
Ajax 請求
公開開發伺服器
Quasar CLI with Webpack - @quasar/app-webpack
PreFetch 功能

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

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

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

它旨在與所有 Quasar 模式(SPA、PWA、SSR、Cordova、Electron)一起使用,但對於 SSR 建置特別有用。

安裝

/quasar.config 檔案

return {
  preFetch: true
}

警告

當您使用它來預先抓取資料時,您需要使用 Vuex Store,因此請確保您的專案資料夾在建立專案時具有 /src/store 資料夾,否則請產生一個新專案並將 store 資料夾內容複製到您目前的專案。

PreFetch 如何協助 SSR 模式

此功能對於 SSR 模式特別有用(但不限於此)。在 SSR 期間,我們基本上是呈現應用程式的「快照」,因此如果應用程式依賴某些非同步資料,則需要在我們開始呈現程序之前預先抓取並解析此資料

另一個考量是,在用戶端,在我們掛載用戶端應用程式之前,需要提供相同的資料 - 否則用戶端應用程式將使用不同的狀態呈現,並且 hydration 將會失敗。

為了處理這個問題,抓取的資料需要存在於視圖元件之外,在專用的資料儲存區或「狀態容器」中。在伺服器上,我們可以在呈現之前預先抓取資料並將資料填入儲存區。用戶端儲存區將在我們掛載應用程式之前直接擷取伺服器狀態。

PreFetch 何時啟動

preFetch hook(在接下來的章節中描述)由造訪的路由決定 - 這也決定了呈現哪些元件。事實上,給定路由所需的資料也是該路由呈現的元件所需的資料。因此,將 hook 邏輯僅放在路由元件內是自然(也是必要的)。 這包括 /src/App.vue,在這種情況下,它只會在應用程式啟動時執行一次。

讓我們舉一個例子,以便了解何時呼叫 hook。假設我們有這些路由,並且我們為所有這些元件編寫了 preFetch hook

路由

[
  {
    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
        }]
      }
    ]
  }
]

現在,讓我們看看當使用者按照下面指定的順序依序造訪這些路由時,如何呼叫 hook。

正在造訪的路由從以下位置呼叫的 Hooks觀察
/App.vue 然後 LandingPageApp.vue hook 在我們的應用程式啟動時呼叫。
/shop/allShopLayout 然後 ShopAll-
/shop/newShopNewShopNew 是 ShopLayout 的子元件,而 ShopLayout 已經呈現,因此 ShopLayout 不會再次呼叫。
/shop/product/pyjamasShopProduct-
/shop/product/shoesShopProductQuasar 注意到相同的元件已呈現,但路由已更新,並且它具有路由參數,因此它再次呼叫 hook。
/shop/product/shoes/overviewShopProduct 然後 ShopProductOverviewShopProduct 具有路由參數,因此即使它已經呈現,也會呼叫它。
/LandingPage-

用法

hook 定義為路由元件上稱為 preFetch 的自訂靜態函式。請注意,由於此函式將在元件實例化之前呼叫,因此它無法存取 this

一些用作路由的 .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 位置物件。

使用 preFetch 初始化 Pinia 或 Vuex Store

preFetch hook 只會在應用程式啟動時執行一次,因此您可以利用這個機會在這裡初始化 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
  }
}
處理 Vuex store

// App.vue

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() hook 中延遲註冊此模組

在路由元件內部

<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>

另請注意,由於該模組現在是路由元件的相依性,因此 Webpack 會將其移至路由元件的非同步區塊中。

警告

別忘了對 registerModule 使用 preserveState: true 選項,以便我們保留伺服器注入的狀態。

搭配 Vuex 和 TypeScript 使用

您可以使用 preFetch 輔助程式來輸入提示 store 參數(否則 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 hook 時使用它。

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()
    })
  }
}