PreFetch 是一项功能(仅在使用 Quasar CLI 时可用),它允许 Vue Router(在 /src/router/routes.js
中定义)拾取的组件执行以下操作:
¥The PreFetch is a feature (only available when using Quasar CLI) which allows the components picked up by Vue Router (defined in /src/router/routes.js
) to:
预取数据
¥pre-fetch data
验证路由
¥validate the route
当某些条件不满足时(例如用户未登录),重定向到其他路由
¥redirect to another route, when some conditions aren’t met (like user isn’t logged in)
可以帮助初始化存储状态
¥can help in initializing the Store state
以上所有内容都将在实际路由组件渲染之前运行。
¥All the above will run before the actual route component is rendered.
它旨在与所有 Quasar 模式(SPA、PWA、SSR、Cordova、Electron)兼容,但对于 SSR 构建尤其有用。
¥It is designed to work with all Quasar modes (SPA, PWA, SSR, Cordova, Electron), but it is especially useful for SSR builds.
安装(Installation)
¥Installation
return {
preFetch: true
}
警告
当你使用它来预取数据时,你可能需要使用 Pinia,因此请确保在创建项目时,项目文件夹中包含 /src/stores
(用于 Pinia)文件夹;否则,请生成一个新项目并将存储文件夹内容复制到当前项目(或使用 quasar new store
命令)。
¥When you use it to pre-fetch data, you may want to use Pinia, so make sure that your project folder has the /src/stores
(for Pinia) folders when you create your project, otherwise generate a new project and copy the store folder contents to your current project (or use quasar new store
command).
PreFetch 如何帮助 SSR 模式(How PreFetch Helps SSR Mode)
¥How PreFetch Helps SSR Mode
此功能对于 SSR 模式特别有用(但不限于此)。在 SSR 期间,我们本质上是在渲染应用的 “snapshot”,因此如果应用依赖于某些异步数据,则需要在开始渲染过程之前预取并解析这些数据。
¥This feature is especially useful for the SSR mode (but not limited to it only). During SSR, we are essentially rendering a “snapshot” of our app, so if the app relies on some asynchronous data, then this data needs to be pre-fetched and resolved before we start the rendering process.
另一个需要注意的问题是,在客户端上,在挂载客户端应用之前,需要提供相同的数据。 - 否则,客户端应用将使用不同的状态进行渲染,并且 hydration 将失败。
¥Another concern is that on the client, the same data needs to be available before we mount the client side app - otherwise the client app would render using a different state and the hydration would fail.
为了解决这个问题,获取的数据需要位于视图组件之外,存储在专用数据存储或 “状态容器” 中。在服务器上,我们可以在渲染之前预取数据并将其填充到存储中。客户端存储将在应用加载之前直接获取服务器状态。
¥To address this, the fetched data needs to live outside the view components, in a dedicated data store, or a “state container”. On the server, we can pre-fetch and fill data into the store before rendering. The client-side store will directly pick up the server state before we mount the app.
何时激活预取功能(When PreFetch Gets Activated)
¥When PreFetch Gets Activated
preFetch
钩子(将在下一节中介绍)由访问的路由决定。 - 这也决定了哪些组件会被渲染。实际上,给定路由所需的数据也是在该路由上渲染的组件所需的数据。因此,将钩子逻辑仅放置在路由组件内部是自然而然的(也是必要的)。这包括 /src/App.vue
,在本例中,它只会在应用启动时运行一次。
¥The preFetch
hook (described in next sections) is determined by the route visited - which also determines what components are rendered. In fact, the data needed for a given route is also the data needed by the components rendered at that route. So it is natural (and also required) to place the hook logic ONLY inside route components. This includes /src/App.vue
, which in this case will run only once at the app bootup.
让我们举一个例子来理解钩子何时被调用。假设我们有这些路由,并且我们已经为所有这些组件编写了 preFetch
钩子:
¥Let’s take an example in order to understand when the hook is being called. Let’s say we have these routes and we’ve written preFetch
hooks for all these components:
[
{
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
}]
}
]
}
]
现在,让我们看看当用户按照下面指定的顺序依次访问这些路由时,这些钩子是如何被调用的。
¥Now, let’s see how the hooks are called when the user visits these routes in the order specified below, one after another.
正在访问的路由 | 调用自以下位置的钩子 | 观察值 |
---|---|---|
/ | 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 | * |
用法(Usage)
¥Usage
该钩子在我们的路由组件上定义为一个名为 preFetch
的自定义静态函数。请注意,由于此函数将在组件实例化之前调用,因此它无法访问 this
。
¥The hook is defined as a custom static function called preFetch
on our route components. Note that because this function will be called before the components are instantiated, it doesn’t have access to this
.
以下示例是使用 Pinia 时的示例:
¥Example below is when using Pinia:
<template>
<div>{{ item.title }}</div>
</template>
<script>
import { useRoute } from 'vue-router'
import { useMyStore } from 'stores/myStore.js'
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:
const myStore = useMyStore() // useMyStore(store) for SSR
return myStore.fetchItem(currentRoute.params.id) // assumes it is async
},
setup () {
const myStore = useMyStore()
const $route = useRoute()
// display the item from store state.
const item = computed(() => myStore.items[$route.params.id])
return { item }
}
}
</script>
如果你正在使用 <script setup>
(以及 Vue 3.3+):
¥If you are using <script setup>
(and 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 对象。
¥If you are developing a SSR app, then you can check out the ssrContext Object that gets supplied server-side.
// related action for Promise example
// ...
actions: {
fetchItem ({ commit }, id) {
return axiosInstance.get(url, id).then(({ data }) => {
this.items = data
})
}
}
// ...
重定向示例(Redirecting Example)
¥Redirecting Example
以下是在某些情况下重定向用户的示例,例如当用户尝试访问只有经过身份验证的用户才能看到的页面时。
¥Below is an example of redirecting the user under some circumstances, like when they try to access a page that only an authenticated user should see.
// We assume here we already wrote the authentication logic
// in one Pinia Store, so take as a high-level example only.
import { useMyStore } from 'stores/myStore'
preFetch ({ store, redirect }) {
const myStore = useMyStore() // useMyStore(store) for SSR
if (!myStore.isAuthenticated) {
redirect({ path: '/login' })
}
}
默认情况下,重定向的状态响应代码为 302,但我们可以在调用函数时将此状态代码作为第二个可选参数传递,如下所示:
¥By default, redirect occurs with a status response code of 302, but we can pass this status code as the second optional parameter when calling the function, like this:
redirect({ path: '/moved-permanently' }, 301)
如果调用 redirect(false)
(仅在客户端支持!),它将中止当前路由导航。请注意,如果你在 src/App.vue
中像这样使用它,它将停止应用启动,这是不可取的。
¥If redirect(false)
is called (supported only on client-side!), it aborts the current route navigation. Note that if you use it like this in src/App.vue
it will halt the app bootup, which is undesirable.
redirect()
方法需要一个 Vue Router 位置对象。
¥The redirect()
method requires a Vue Router location Object.
使用 preFetch 初始化 Pinia(Using preFetch to Initialize Pinia)
¥Using preFetch to Initialize Pinia
preFetch
钩子仅在应用启动时运行一次,因此你可以利用这个机会在此处初始化 Pinia 存储。
¥The preFetch
hook runs only once, when the app boots up, so you can use this opportunity to initialize the Pinia store(s) here.
// App.vue - handling Pinia stores
// example with a store named "myStore"
// placed in /src/stores/myStore.js|ts
import { useMyStore } from 'stores/myStore.js'
export default {
// ...
preFetch () {
const myStore = useMyStore()
// do something with myStore
}
}
加载状态(Loading State)
¥Loading State
良好的用户体验包括在用户等待页面准备就绪时通知用户后台正在处理某些操作。Quasar CLI 为此提供了两个开箱即用的选项。
¥A good UX includes notifying the user that something is being worked on in the background while he/she waits for the page to be ready. Quasar CLI offers two options for this out of the box.
LoadingBar(LoadingBar)
当你将 Quasar LoadingBar 插件添加到你的应用时,Quasar CLI 将在运行 preFetch 钩子时默认使用它。
¥When you add Quasar LoadingBar plugin to your app, Quasar CLI will use it while it runs the preFetch hooks by default.
加载中(Loading)
¥Loading
也可以使用 Quasar 加载中 插件。以下是示例:
¥There’s also the possibility to use Quasar Loading plugin. Here’s an example:
import { Loading } from 'quasar'
export default {
// ...
preFetch ({ /* ... */ }) {
Loading.show()
return new Promise(resolve => {
// do something async here
// then call "resolve()"
}).then(() => {
Loading.hide()
})
}
}