为什么捐赠
API 浏览器
联系站长
Quasar CLI with Vite - @quasar/app-vite
预取特性 PreFetch

预取(PreFetch)是一个 仅在使用 Quasar CLI 时可用 的特性,它可以让被 Vue Router 选中的组件(在 /src/router/routes.js 中定义的组件)拥有以下额外能力:

  • 预取数据
  • 验证路由
  • 当不满足条件时进行路由重定向(例如用户未登录时)
  • 帮助初始化 Store 数据

上述所有工作都会在实际的路由组件渲染之前完成。

这个特性在所有的 Quasar 开发模式中都可用(SPA、PWA、SSR、Cordova、Electron),但它对 SSR 构建尤其有用。

安装

/quasar.config file

return {
  preFetch: true,
};

警告

当您使用预取来获取数据时,您可能需要使用 Pinia 来存储数据,所以请确保在创建项目时项目中已经包含了 /src/stores(Pinia)目录,否则请创建一个新的项目并将 store 目录的内容复制到您当前的项目中(或者使用 quasar new store 命令)。

为什么 PreFetch 对 SSR 很有用

这个特性对 SSR 开发模式尤其有用(但并不仅限于 SSR 模式)。在 SSR 中,我们实质上是在渲染应用的一个"快照",所以当应用依赖一些异步数据时,这些数据需要在渲染过程开始之前预取并准备好

另一个问题是,在客户端,同样的数据需要在挂载客户端应用之前就可用——否则客户端应用会使用不同的状态进行渲染,导致水合(hydration)失败。

为了解决这个问题,获取的数据需要存放在视图组件之外,放在一个专门的数据仓库(data store)或"状态容器"中。在服务端,我们可以在渲染之前预取数据并填充到 store 中。客户端的 store 则会在挂载应用之前直接接收服务端的状态。

PreFetch 钩子何时被激活

preFetch 钩子(将在下一节中详细介绍)由访问的路由决定——而路由同时也决定了哪些组件会被渲染。事实上,给定路由所需的数据,也正是在该路由上渲染的组件所需的数据。因此,将 preFetch 钩子的逻辑只放在路由组件内部,既自然又必要。 这也包括 /src/App.vue,它的钩子只会在应用启动时执行一次。

让我们通过一个例子来理解钩子何时被调用。假设我们有以下路由,并为所有这些组件都编写了 preFetch 钩子:

Routes

[
  {
    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/allShopLayout 然后 ShopAll-
/shop/newShopNewShopNew 是 ShopLayout 的子组件,而 ShopLayout 已经渲染过了,所以 ShopLayout 中的钩子不会再次被调用。
/shop/product/pyjamasShopProduct-
/shop/product/shoesShopProductQuasar 发现相同的组件已经在渲染了,但路由更新了且包含路由参数,因此会再次调用钩子。
/shop/product/shoes/overviewShopProduct 然后 ShopProductOverviewShopProduct 包含路由参数,所以即使它已经被渲染过,钩子也会再次被调用。
/LandingPage-

用法

钩子是在路由组件中定义的一个名为 preFetch 的自定义静态函数。注意,因为这个函数会在组件实例化之前被调用,所以它无法访问 this

以下是使用 Pinia 的示例:

Some .vue component used as route

<template>
  <div>{{ item.title }}</div>
</template>

<script>
  import { useRoute } from "vue-router";
  import { useMyStore } from "stores/myStore.js";

  export default {
    // 我们的钩子在这里
    preFetch({ store, currentRoute, previousRoute, redirect, ssrContext, urlPath, publicPath }) {
      // 在这里可以获取数据、验证路由、重定向路由等等...

      // ssrContext 仅在 SSR 模式的服务端可用

      // 这里无法访问 "this"

      // 如果执行了异步任务,请返回一个 Promise
      // 示例:
      const myStore = useMyStore(); // SSR 模式下使用 useMyStore(store)
      return myStore.fetchItem(currentRoute.params.id); // 假设这是一个异步操作
    },

    setup() {
      const myStore = useMyStore();
      const $route = useRoute();

      // 展示 store 中的 item 数据
      const item = computed(() => myStore.items[$route.params.id]);

      return { item };
    },
  };
</script>

如果您在使用 <script setup>(且 Vue 3.3+):

<script setup>
  /**
   * defineOptions 是一个宏。
   * 选项将会被提升到模块作用域中,
   * 无法访问 <script setup> 中不是字面常量的局部变量。
   */
  defineOptions({
    preFetch() {
      console.log("running preFetch");
    },
  });
</script>

提示

如果您在开发 SSR 应用,可以查看服务端提供的 ssrContext 对象。

// 异步任务相关的 action 示例
// ...

actions: {
  fetchItem ({ commit }, id) {
    return axiosInstance.get(url, id).then(({ data }) => {
      this.items = data
    })
  }
}

// ...

重定向示例

下面是在某些情况下重定向用户的示例,比如当用户试图访问仅限已认证用户查看的页面时。

// 这里假设我们已经在一个 Pinia Store 中编写了身份验证逻辑,
// 仅作为高层次示例参考。
import { useMyStore } from 'stores/myStore'

preFetch ({ store, redirect }) {
  const myStore = useMyStore() // SSR 模式下使用 useMyStore(store)
  if (!myStore.isAuthenticated) {
    redirect({ path: '/login' })
  }
}

默认情况下,重定向会返回 302 状态码,但我们可以将状态码作为第二个可选参数传入,像这样:

redirect({ path: "/moved-permanently" }, 301);

如果调用了 redirect(false)(仅在客户端支持!),则会中止当前的路由导航。请注意,如果在 src/App.vue 中这样使用,将会阻止应用的启动,这是不可取的。

redirect() 方法需要传入一个 Vue Router 的 location 对象。

使用 preFetch 来初始化 Pinia

当应用启动时,preFetch 钩子只会运行一次,所以您可以借此机会来初始化 Pinia store。


// App.vue - 处理 Pinia stores
// 以一个名为 "myStore" 的 store 为例
// 位于 /src/stores/myStore.js|ts

import { useMyStore } from 'stores/myStore.js'

export default {
  // ...
  preFetch () {
    const myStore = useMyStore()
    // 对 myStore 执行一些操作
  }
}

加载状态

一个好的用户体验应当在后台工作进行时告知用户正在加载中,让用户耐心等待页面就绪。Quasar CLI 为此提供了两种开箱即用的方案。

LoadingBar

当您为应用添加了 Quasar 的 LoadingBar 插件时,Quasar CLI 默认会在 preFetch 钩子运行期间自动显示加载进度条。

Loading

也可以使用 Quasar 的 Loading 插件。示例如下:

A route .vue component

import { Loading } from "quasar";

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

    return new Promise((resolve) => {
      // 在这里执行异步操作
      // 然后调用 "resolve()"
    }).then(() => {
      Loading.hide();
    });
  },
};