为什么捐赠
API 资源管理器
升级指南
新增!
quasar.config 文件
转换为使用 Webpack 的 CLI
浏览器兼容性
支持 TypeScript
目录结构
命令列表
CSS 预处理器
路由
延迟加载 - 代码分割
处理资源
启动文件
预取功能
API 代理
处理 Webpack
处理 process.env
使用 Pinia 进行状态管理
使用 Vuex 进行状态管理
代码风格检查
测试与审计
开发移动应用
Ajax 请求
将开发服务器开放给公众
Quasar CLI 与 Webpack - @quasar/app-webpack
预取功能

预取是一个功能(**仅在使用 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 文件夹内容复制到当前项目。

预取如何帮助 SSR 模式

此功能对于 SSR 模式尤其有用(但不仅限于此)。在 SSR 期间,我们实际上是在渲染应用程序的“快照”,因此如果应用程序依赖于某些异步数据,**则需要在开始渲染过程之前预取并解析此数据**。

另一个问题是在客户端,相同的数据需要在挂载客户端应用程序之前可用 - 否则客户端应用程序将使用不同的状态进行渲染,并且水合将失败。

为了解决这个问题,获取的数据需要存在于视图组件之外,在专用数据存储或“状态容器”中。在服务器端,我们可以在渲染之前预取并将数据填充到存储中。客户端存储将在我们挂载应用程序之前直接获取服务器状态。

何时激活预取

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/allShopLayout 然后 ShopAll-
/shop/newShopNewShopNew 是 ShopLayout 的子组件,并且 ShopLayout 已经渲染,因此不会再次调用 ShopLayout。
/shop/product/pyjamasShopProduct-
/shop/product/shoesShopProductQuasar 注意到相同的组件已渲染,但路由已更新并且具有路由参数,因此它会再次调用钩子。
/shop/product/shoes/overviewShopProduct 然后 ShopProductOverviewShopProduct 具有路由参数,因此即使它已渲染也会被调用。
/LandingPage-

用法

钩子定义为我们路由组件上的名为 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(s)

preFetch 钩子仅运行一次,在应用程序启动时,因此您可以利用此机会在此处初始化 Pinia store(s) 或 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() 钩子中延迟注册此模块

在路由组件内部

<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 参数添加类型,即使使用普通语法,其他参数也会自动添加类型。

加载状态

良好的用户体验包括在用户等待页面加载时,通知用户后台正在处理某些操作。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()
    })
  }
}