四、添加视图、路由和过渡

在上一章中,您为使用前端 UI 框架和库创建了一些简单的页面、路由甚至布局,但它们只是非常基本的。因此,在本章中,我们将深入探讨其中的每一个,以及 Nuxt 中的模板。您将自定义默认模板和布局,并创建自定义模板和布局。您还将学习如何自定义全局元标记,并将特定标记添加到 appchild 页面的各个页面。这是有用的,如果信息的。您将为转换页面创建 CSS 和 JavaScript 转换以及动画。因此,在本章结束时,您将能够根据本章所学内容和上一章所学内容提供一个简单但功能全面的 web 应用或网站(带有一些虚拟数据)。

**我们将在本章中介绍的主题如下:

  • 创建自定义管线
  • 创建自定义视图
  • 创建自定义转换

创建自定义管线

如果我们想了解路由在 Nuxt 中的工作原理,首先应该了解它在 Vue 中的工作原理。然后我们就可以了解如何在 Nuxt 应用中实现它。传统 Vue 应用中的自定义路由是通过 Vue 路由创建的。那么,让我们从了解什么是 Vue 路由开始。

Vue 路由简介

Vue 路由是一个 Vue 插件,它允许您在单页应用(SPA)中创建健壮的页面导航路由,而无需刷新页面。例如,如果我们想要有一个User组件,该组件可用于所有用户,但具有不同的用户 ID,则可以快速使用。您可以按如下方式使用此组件:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

在本例中,任何带有 ID(例如,/user/1user/2)的/user路由将被定向到User组件,该组件将使用 ID 呈现模板。这仅在安装 Vue 插件时才可能实现,因此让我们在下一节中了解如何为 Vue 应用安装它,在学习如何在 Nuxt 应用中工作之前。

For more information about Vue Router, visit https://router.vuejs.org/.

安装 Vue 路由

在 Vue 中,您必须明确安装 Vue 路由,以便在传统的 Vue 应用中创建路由。即使您正在使用 Vue CLI(我们将在第 11 章编写路由中间件和服务器中间件中介绍),您也必须从提示选择的选项中选择手动选择功能以选择路由,以选择您需要的功能。因此,让我们来看看如何在本节中安装它。安装 Vue 路由有两个选项:

  • 您可以使用 npm:
$ npm install vue-router

然后,在 app root 中,通过Vue.use()显式导入vue-router

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)
  • 或者,您可以使用 CDN 或直接下载:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vue-router.js"></script>

如果您使用的是 CDN,只需在 Vue core 之后添加vue-router,其余的安装将自行完成。安装完 Vue 路由后,可以使用它创建路由。

使用 Vue 路由创建路由

如果您正在使用 CDN 选项,那么首先,在项目根目录中创建一个具有以下基本 HTML 结构的.html文件,并在<head>块中包含 CDN 链接:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    //...
  </body>
</html>

之后,您可以通过以下步骤快速启动 Vue 路由:

  1. 使用<body>块中的以下标记创建应用库:
<div id="app">
  <h1>Hello App!</h1>
  <p>
    <router-link to="/about">About</router-link>
    <router-link to="/contact">Contact</router-link>
  </p>
  <router-view></router-view>
</div>
<script type="text/javascript">
  //...
</script>

<router-link>组件用于指定目标位置,并将呈现为带有href<a>标记,而<router-view>组件用于呈现请求的内容,这是我们将在下一步创建的 Vue 组件。

  1. <script>块中定义两个 Vue 组件:
const About = { template: '<div>About</div>' }
const Contact = { template: '<div>Contact</div>' }
  1. 创建一个名为routes的常量变量,并使用与<router-link>中的链接匹配的路径将 Vue 组件添加到component属性中:
const routes = [
  { path: '/about', component: About },
  { path: '/contact', component: Contact }
]
  1. 使用new操作符创建路由实例,并传入routes常量:
const router = new VueRouter({
  routes
})

Note that route in the preceding code block is a short form (shorthand property name) for routes: routes in ES6/ES2015. For more information about the shorthand property name, please visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer.

  1. 使用new操作符创建一个 Vue 实例并传入router实例,然后将#app元素挂载到根实例:
const app = new Vue({
  router
}).$mount('#app')
  1. 在浏览器中运行应用,然后您应该会在屏幕上看到关于和联系人的链接。当您导航到/about/contact时,您应该会看到它们的组件在屏幕上按预期成功呈现。

You can find the code for the preceding the app in /chapter-4/vue/vue-route/basic.html in our GitHub repository and run it in your favorite browser to see how it works.

现在,让我们探索 Nuxt 如何通过 Vue 路由为我们生成前面的路由。在 Nuxt 中创建路由的过程更简单,因为vue-router在 Nuxt 中是现成的。这意味着从技术上讲,您可以跳过传统 Vue 应用中的前一个安装步骤。您还可以跳过前面的 JavaScript 步骤–步骤35。Nuxt 将扫描/pages/目录中的.vue文件树,并自动为您生成路由。那么,让我们来探索 Nuxt 如何为您创建和处理路由。我们将首先创建基本路线。

创建基本路线

只需将具有固定文件名的.vue文件添加到/pages/目录,即可创建基本路由。您还可以通过将.vue文件组织到不同的文件夹中来创建子路由。以以下为例:

pages/
--| users/
-----| index.vue
-----| john-doe.vue
--| index.vue

然后,Nuxt 将为您生成以下路由,而无需编写任何路由:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users',
      path: '/users',
      component: 'pages/users/index.vue'
    },
    {
      name: 'users-john-doe',
      path: '/users/john-doe',
      component: 'pages/users/john-doe.vue'
    }
  ]
}

You can find this example app in /chapter-4/nuxt-universal/routing/basic-routes/ in our GitHub repository.

您应该熟悉上一章中的这些基本路线。这种类型的路由适用于顶级页面,例如/about/contact/posts。但是,如果在每个顶级页面中都有多个子页面,并且它们会随着时间的推移而动态增加,那么应该使用动态路由来处理这些子页面的路由。让我们在下一节中了解如何创建动态管线。

创建动态路由

使用下划线时,动态路由由 Nuxt 生成。在更复杂的应用中,动态路由是有用且不可避免的。因此,如果您想创建动态路由,那么只需创建一个带有前缀下划线的.vue文件(或目录),后跟文件(或目录)的名称。以以下为例:

pages/
--| _slug/
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

然后,您将从 Nuxt 获得以下路由,而无需编写任何路由:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    }
  ]
}

You can find this example app in /chapter-4/nuxt-universal/routing/dynamic-routes/ in our GitHub repository.

动态路由适用于共享相同布局的页面。例如,如果您有使用相同布局的/about/contact路由(这不太可能),那么前面动态路由示例代码中的/_slug/目录是一个不错的选择。因此,正如/users路由下共享相同布局的子页面一样,/_id.vue文件方法也是此场景的良好选择。

除了使用这个(简单的)动态路由为/users路由下的子页面创建子路由外,我们还可以为它们使用更复杂的动态路由–嵌套路由。在呈现子页面时,如果不希望父布局完全替换为子布局,则会出现这种情况;换句话说,当您想要在父布局的内部呈现子页面时。让我们在下一节中了解如何实现这一点。

创建嵌套管线

简而言之,由嵌套组件生成的管线称为嵌套管线。在某些情况下,您可能希望组合嵌套在其他组件(父组件)中的组件(子组件),并且希望在父组件的特定视图中呈现这些子组件,而不是让子组件替换父组件。

要在 Vue 应用中执行此操作,您需要在子组件的父组件中插入一个<router-view>组件。例如,假设您有一个Users父组件,并且您希望在调用特定用户时将单个用户的内容加载到此父组件中。然后,可以通过以下步骤为它们创建嵌套管线:

  1. 创建父组件:
const Users = {
  template: `
    <div class="user">
      <h2>Users</h2>
      <router-link to="/user/1">1</router-link>
      <router-link to="/user/2">2</router-link>
      <router-link to="/user/3">3</router-link>
      <router-view></router-view>
    </div>
  `
}

如果将前面的代码放在图表中,则可以直观地进行解释,如下所示:

+-------------------+
| users             |
| +---------------+ |
| | 1, 2, 3       | |
| +---------------+ |
| +---------------+ |
| | <router-view> | |
| +---------------+ |
+-------------------+
  1. 创建将显示单个用户的内容或信息的子组件:
const User = { template: '<div>User {{ $route.params.id }}</div>' }
  1. 使用children属性创建嵌套路由,如下所示:
const routes = [
  {
    path: '/users',
    component: Users,
    children: [
      {
        path: ':id',
        component: User,
        name: 'user-id'
      }
    ]
  }
]
  1. 在将路由注入 Vue 根实例之前,定义路由实例并传入前面的嵌套路由,如下所示:
const router = new VueRouter({
  routes
})

const app = new Vue({
  router
}).$mount('#app')

然后,当单击子链接时,前面的代码将产生以下视觉效果:;例如,动态生成子编号1/users/1作为其路由:

/users/1
+-------------------+
| users             |
| +---------------+ |
| | 1, 2, 3       | |
| +---------------+ |
| +---------------+ |
| | User 1        | |
| +---------------+ |
+-------------------+
  1. 但是我们还没有完成,因为我们仍然需要在/users中处理空视图,而此时还没有调用任何用户。因此,为了解决这个问题,您将创建一个索引子组件,如下所示:
const Index = { template: '<div>Users Index</div>' }
  1. path键上用空字符串''将前面的索引组件添加到children块:
const routes = [
  {
    path: '/users',
    component: Users,
    children: [
      {
        path: '',
        component: Index,
        name: 'user-index'
      },
      //...
    ]
  }
]
  1. 那么现在,如果您在浏览器中导航到/users,您应该会得到以下结果:
/users
+-------------------+
| users             |
| +---------------+ |
| | 1, 2, 3       | |
| +---------------+ |
| +---------------+ |
| | Users Index   | |
| +---------------+ |
+-------------------+

您可以看到,children选项只是另一个路由配置对象数组,就像routes常量本身一样。因此,您可以根据需要保留嵌套视图。但是,为了更好地维护,我们应该避免深度嵌套,以使应用尽可能简单。

You can find the preceding example code in /chapter-4/vue/vue-route/nested-route.html in our GitHub repository.

这在 Nuxt 中是相同的;您可以使用vue-router的子路由创建嵌套路由。如果要定义嵌套管线的父组件,只需创建与包含子视图的目录同名的 Vue 文件。以以下为例:

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

Nuxt 将自动生成以下路由:

router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

您可以看到,Nuxt 生成的路由与您在 Vue 应用中生成的路由相同。请注意,在 Nuxt 中,我们将<nuxt-child/>包含在父组件中(即.vue文件),而在 Vue 中,我们将<router-view></router-view>包含在父组件中,就像前面的User示例一样。让我们通过练习更好地理解这一点,就像我们在 Vue 应用中所做的那样:

  1. 使用<nuxt-child/>组件创建父组件:
// pages/users.vue
<template>
  <div>
    <h1>Users</h1>
    <nuxt-child/>
  </div>
</template>
  1. 创建索引子组件以保存用户列表:
// pages/users/index.vue
<template>
  <ul>
    <li v-for="user in users" v-bind:key="user.id">
      <nuxt-link :to="`users/${user.id}`">
        {{ user.name }}
      </nuxt-link>
    </li>
  </ul>
</template>

<script>
import axios from 'axios'
export default {
  async asyncData () {
    let { data } = await 
    axios.get('https://jsonplaceholder.typicode.com/users')
    return { users: data }
  }
}
</script>

Note that we will cover the asyncData method in the upcoming section of this chapter and axios in Chapter 5, Adding Vue Components, so do not worry about them at this stage.

  1. 创建一个单独的子组件,其中包含返回子索引页的链接:
// pages/users/_id.vue
<template>
  <div v-if="user">
    <h2>{{ user.name }}</h2>
    <nuxt-link class="button" to="/users">
      Users
    </nuxt-link>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  async asyncData ({ params }) {
    let { data } = await 
    axios.get('https://jsonplaceholder.typicode.com/users/'
     + params.id)
    return { user: data }
  }
}
</script>

您可以看到,Nuxt 使用children属性(如前面的 Vue 应用示例的s步骤 3所示)将您从配置必须在 Vue 应用中执行的嵌套路由中解救出来。

因此,在这个 Nuxt 应用中,users.vue中的<h1>Users</h1>元素在子页面之后呈现时将始终可见。包含列表元素的<ul>元素将始终被子页面替换。如果父级信息在整个子页面中都是持久的,那么这非常有用,因为在呈现子页面时,您不必重新请求父级信息。

You can find this example app in /chapter-4/nuxt-universal/routes/nested-routes/ in our GitHub repository.

由于存在用于“升级”基本路由的动态路由,您可能会问,嵌套路由的动态路由如何?从技术上讲,这是可能的,它们被称为动态嵌套路由。那么,让我们在下一节中了解更多关于它们的信息。

创建动态嵌套路由

我们已经分别了解了动态路由和嵌套路由的工作原理,因此从理论上和技术上讲,可以通过在动态父级(例如,_topic中具有动态子级(例如,_subTopic)来结合这两个选项来创建动态嵌套路由。以下示例结构最好地说明了这一点:

pages/
--| _topic/
-----| _subTopic/
--------| _slug.vue
--------| index.vue
-----| _subTopic.vue
-----| index.vue
--| _topic.vue
--| index.vue

Nuxt 将自动生成以下路由:

router: {
  routes: [
    {
      path: '/',
      component: 'pages/index.vue',
      name: 'index'
    },
    {
      path: '/:topic',
      component: 'pages/_topic.vue',
      children: [
        {
          path: '',
          component: 'pages/_topic/index.vue',
          name: 'topic'
        },
        {
          path: ':subTopic',
          component: 'pages/_topic/_subTopic.vue',
          children: [
            {
              path: '',
              component: 'pages/_topic/_subTopic/index.vue',
              name: 'topic-subTopic'
            },
            {
              path: ':slug',
              component: 'pages/_topic/_subTopic/_slug.vue',
              name: 'topic-subTopic-slug'
            }
          ]
        }
      ]
    }
  ]
}

您可以看到,路由更复杂,这会使您的应用更难开发,只需阅读并试图理解文件目录树,因为它非常抽象,如果它变得“更大”,在某些时候可能会过于抽象。最好的做法是始终将我们的应用设计和结构尽可能简单。以下示例路线是此类路线的一个很好的示例:

  • /_topic/的一些示例如下:
/science
/arts
  • /_topic/_subTopic/的一些示例如下:
/science/astronomy
/science/geology
/arts/architecture
/arts/performing-arts
  • /_topic/_subTopic/_slug.vue的一些示例如下:
/science/astronomy/astrophysics
/science/astronomy/planetary-science
/science/geology/biogeology
/science/geology/geophysics
/arts/architecture/interior-architecture
/arts/architecture/landscape-architecture
/arts/performing-arts/dance
/arts/performing-arts/music

You can find an example app for this type of route in /chapter-4/nuxt-universal/routing/dynamic-nested-routes/ in our GitHub repository.

创建动态路由和页面始终需要路由中的参数(换句话说,路由参数),以便我们可以将它们(无论是 ID 还是 slug)传递给要处理的动态页面。但在处理和响应参数之前,最好先验证它们。那么,让我们看看如何验证下一个主题中的路由 PARAMs。

验证路由参数

在异步处理或获取任何进一步的数据之前,您可以在组件中使用validate方法来验证动态路由的参数。此验证始终返回true继续前进;Nuxt 将停止路由,如果它得到一个false布尔值,将立即抛出 404 错误页面。例如,您希望确保 ID 必须是一个数字:

// pages/users/_id.vue
export default {
  validate ({ params }) {
    return /^\d+$/.test(params.id)
  }
}

因此,如果您请求带有localhost:3000/users/xyz的页面,您将获得一个带有This page could not be found消息的 404 页面。如果要自定义 404 消息,可以使用throw语句对Error对象抛出异常,如下所示:

// pages/users/_id.vue
export default {
  validate ({ params }) {
    let test = /^\d+$/.test(params.id)
    if (test === false) {
      throw new Error('User not found')
    }
    return true
  }
}

您也可以使用asyncvalidate方法进行await操作:

async validate({ params }) {
  // ...
}

您也可以在validate方式中使用return承诺:

validate({ params }) {
  return new Promise(...)
}

You can find the preceding example app on ID validation in /chapter-4/nuxt-universal/routing/validate-route-params/ in our GitHub repository.

验证路由参数是处理无效或未知路由的一种方法,但另一种处理方法是使用_.vue文件捕获它们。那么,让我们在下一节中了解如何进行。

使用.vue 文件处理未知路由

除了使用validate方法抛出常规404 页面外,还可以使用_.vue文件抛出自定义错误页面。让我们通过以下步骤来探索它的工作原理:

  1. /pages/目录中创建一个空的_.vue文件,如下所示:
pages/
--| _.vue
--| index.vue
--| users.vue
--| users/
-----| _id.vue
-----| index.vue
  1. 将任何自定义内容添加到此_.vue文件,如下所示:
// pages/_.vue
<template>
  <div>
    <h1>Not found</h1>
    <p>Sorry, the page you are looking for is not found.</p>
  </div>
</template>
  1. 启动应用并导航到以下路径,您将看到 Nuxt 将调用此_.vue文件来处理与正确路径不匹配的任何级别的请求:
/company
/company/careers
/company/careers/london
/users/category/subject
/users/category/subject/type
  1. 如果您想在特定级别上抛出更具体的 404 页面–例如,仅在/users路由中–然后在/users/文件夹中创建另一个_.vue文件,如下所示:
pages/
--| _.vue
--| index.vue
--| users.vue
--| users/
-----| _.vue
-----| _id.vue
-----| index.vue
  1. 为此_.vue文件添加自定义内容,如下所示:
// pages/users/_.vue
<template>
  <div>
    <h1>User Not found</h1>
    <p>Sorry, the user you are looking for is not found.</p>
  </div>
</template>
  1. 再次导航到以下路径,您将看到 Nuxt 不再为不匹配的请求调用此/pages/_.vue文件:
/users/category/subject
/users/category/subject/type

相反,Nuxt 现在调用/pages/users/_.vue文件来处理它们。

You can find this example app in /chapter-4/nuxt-universal/routing/unknown-routes/ in our GitHub repository.

我们希望到目前为止,您应该知道如何以各种方式创建适合您的应用的路由,但是路由和页面在 Nuxt 中是密不可分的,因此您还需要知道如何创建 Nuxt 页面,这是自定义视图。您将在下一个主题中学习如何做到这一点。

创建自定义视图

您在上面的自定义路由中创建的每个路由都将登录到一个“页面”,该页面包含我们希望在前端显示的所有 HTML 标记和内容。从软件架构的角度来看,此 HTML 标记和内容(包括元信息、图像和字体)是应用的视图或表示层。在 Nuxt 中,我们可以轻松创建和自定义视图。让我们来了解 Nuxt 视图的组成部分以及如何对其进行自定义。

理解 Nuxt 视图

Nuxt 中的视图结构由应用模板、HTML 标题、布局和页面层组成。您可以使用它们为应用路由创建视图。在更复杂的应用中,您可以使用 API 中的数据填充它们,而在简单的应用中,您可以直接手动将虚拟数据嵌入其中。在接下来的部分中,我们将引导您了解这些层中的每一层。在此之前,请花点时间研究下图,它将为您提供 Nuxt 视图的完整视图:

Reference source: https://nuxtjs.org/guide/views

您可以看到 Document-HTML 文件是 Nuxt 视图的最外层,其次是布局、页面以及可选的页面子层和 Vue 组件层。Document-HTML 文件是 Nuxt 应用的应用模板。让我们先看看这个最基本的层,然后在下一节中学习如何自定义它。

自定义应用模板

Nuxt 在幕后为您创建 HTML 应用模板,因此基本上,您不必费心创建它。但是,如果需要,您仍然可以通过添加脚本或样式等方式对其进行自定义。默认的 Nuxt HTML 模板如下所示:

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
 <head>
 {{ HEAD }}
 </head>
 <body {{ BODY_ATTRS }}>
 {{ APP }}
 </body>
</html>

如果要更改或覆盖此默认值,只需在根目录中创建一个app.html文件。以以下为例:

// app.html
<!DOCTYPE html>
<!--[if IE 9]><html lang="en-US" class="lt-ie9 ie9" {{ HTML_ATTRS }}><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--><html {{ HTML_ATTRS }}><!--<![endif]-->
  <head>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

重新启动你的应用,你会看到你的自定义应用 HTML 模板已经替换了 Nuxt 中的默认模板。

You can find this example in /chapter-4/nuxt-universal/view/app-template/ in our GitHub repository.

与 HTML 文档(它是一个<html>元素)最接近的第二层是 HTML head,<head>元素,它包含重要的元信息以及页面的脚本和样式。我们不直接在应用模板中添加或自定义这些数据,而是在 Nuxt 配置文件和/pages/目录中的文件中添加或自定义这些数据。那么,让我们在下一节中了解如何进行。

创建自定义 HTML 头

HTML<head>元素由<title><style><link><meta>元素组成。手动添加这些元素可能是一项乏味的任务。因此,Nuxt 在应用中为您处理这些问题。在第 2 章Nuxt入门中,您了解到它们是 Nuxt 从 Nuxt 配置文件中 JavaScript 对象中的数据为您生成的,这些对象使用大括号({}编写,如下所示:

// nuxt.config.js
export default {
  head: {
    title: 'Default Title',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'parent' }
    ],
    link: [
      { rel: 'icon', type: 'img/x-icon', href: '/favicon.ico' }
    ]
  }
}

在本主题中,我们感兴趣的是 Nuxt 配置文件中的meta块和/pages/目录中的页面。Nuxt 使用 Vue 元插件来管理这些元属性。因此,要了解它在 Nuxt 中的工作原理,我们首先应该了解 Vue Meta 在传统 Vue 应用中的工作原理。

介绍 Vue-Meta

Vue Meta 是一个 Vue 插件,用于管理和创建具有 Vue 内置反应性的 HTML 元数据。您只需将metaInfo特殊属性添加到任何 Vue 组件中,它将自动呈现为 HTML 元标记,如下所示:

// Component.vue
export default {
  metaInfo: {
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' }
    ]
  }
}

前面的 JavaScript 代码块将在页面中呈现为以下 HTML 标记:

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

For more information about Vue Meta, visit https://vue-meta.nuxtjs.org/.

您可以看到,您需要做的只是在 JavaScript 对象中提供元数据。现在,让我们安装它,并了解如何为 Vue 应用配置它。

安装 Vue Meta

与所有其他 Vue 插件一样,您可以通过以下步骤安装 Vue Meta 并将其连接到 Vue 应用:

  1. 通过 npm 安装 Vue Meta:
$ npm i vue-meta

或者,您可以通过带有<script>元素的 CDN 进行安装,如下所示:

<script src="https://unpkg.com/vue-meta@1.5.8/lib/vue-meta.js"></script>
  1. 如果您正在编写 ES6 JavaScript 应用,请在主应用文件中使用 Vue 路由导入 Vue Meta:
//main.js
import Vue from 'vue'
import Router from 'vue-router'
import Meta from 'vue-meta'

Vue.use(Router)
Vue.use(Meta)
export default new Router({
  //...
})
  1. 然后,您可以在任何 Vue 组件中使用它,如下所示:
// app.vue
var { data } = await axios.get(...)
export default {
  metaInfo () {
    return {
      title: 'Nuxt',
      titleTemplate: '%s | My Awesome Webapp',
      meta: [
        { vmid: 'description', name: 'description', content: 'My
          Nuxt portfolio' }
      ]
    }
  }
}

在本例中,由于我们使用axios异步获取数据,因此必须使用metaInfo方法从异步数据中注入元信息,而不是使用metaInfo属性。您甚至可以使用titleTemplate选项为页面标题添加模板,就像前面的示例一样。接下来,我们将使用这个插件创建一个简单的 Vue 应用,这样您就可以更全面地了解如何使用它。

在 Vue 应用中使用 Vue Meta 创建元数据

和往常一样,我们可以在单个 HTML 页面上启动并运行 Vue 应用。让我们开始吧:

  1. <head>块中包括 CND 链接:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-meta@1.5.8/lib/vue-meta.js"></script>
  1. 使用<script>块中的元数据创建以下组件:
const About = {
  name: 'about',
  metaInfo: {
    title: 'About',
    titleTemplate: null,
    meta: [
      { vmid: 'description', name: 'description', content: 'About 
        my Nuxt...' 
      }
    ]
  }
}
const Contact = {
  name: 'contact',
  metaInfo: {
    title: 'Contact',
    meta: [
      { vmid: 'description', name: 'description', content: 
       'Contact me...' }
    ]
  }
}
  1. 然后,在根实例中添加默认元数据:
const app = new Vue({
  metaInfo: {
    title: 'Nuxt',
    titleTemplate: '%s | My Awesome Webapp',
    meta: [
      { vmid: 'description', name: 'description', content: 'My 
        Nuxt portfolio' 
      }
    ]
  },
  router
}).$mount('#app')

注意,我们可以通过简单地将null添加到子组件中的titleTemplate选项来覆盖组件中的默认元模板,就像前面的About组件一样。

You can find this example app in /chapter-4/vue/vue-meta/basic.html in our GitHub repository.

在本例中,由于我们没有使用axios异步获取数据,因此我们可以直接使用metaInfo属性,而不是使用metaInfo方法将元信息注入异步数据。然后,当您在刚刚创建的路线周围导航时,您将在浏览器中看到页面标题和元信息的更改。在 Vue 应用中使用此插件非常容易,不是吗?现在,我们应该在下一节中了解它在 Nuxt 应用中的工作原理。

自定义 Nuxt 应用中的默认元标记

在 Nuxt 应用中创建和自定义元信息更简单,因为默认情况下,Vue 元在 Nuxt 中出现。这意味着您不必像在 Vue 应用中那样安装它。您只需使用 Nuxt 配置文件中的head属性来定义应用的默认<meta>标记,如下所示:

// nuxt.config.js
head: {
  title: 'Nuxt',
  titleTemplate: '%s | My Awesome Webapp',
  meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    { hid: 'description', name: 'description', content: 'My 
      Nuxt portfolio' }
  ]
}

但是,Nuxt 和 Vue 应用之间的区别在于,Nuxt 中必须使用hid键,而 Vue 中必须使用vmid。在子组件中定义元标记(元标记)时,您应该始终对元元素使用hid,以防止元标记的重复。另外,请注意,metaInfo键仅在 Vue 中使用,而title键在 Nuxt 中使用,用于添加元信息。

因此,这就是如何为 Nuxt 应用添加和自定义标题和元标记的方法。但是,它们是全局添加的,这意味着它们将应用于应用中的所有页面。那么,如何将它们专门添加到页面并覆盖 Nuxt 配置文件中的全局值呢?让我们在下一节中找到答案。

为 Nuxt 页面创建自定义元标记

如果要为特定页面添加自定义元标记或覆盖 Nuxt 配置文件中的默认元标记,只需直接在该特定页面上使用head方法,该方法将返回包含titlemeta选项数据的 JavaScript 对象,如下所示:

// pages/index.vue
export default {
  head () {
    return {
      title: 'Hello World!',
      meta: [
        { hid: 'description', name: 'description', content: 'My 
          Nuxt portfolio' }
      ]
    }
  }
}

然后,您将获得此页面的输出:

<title data-n-head="true">Hello World! | My Awesome Webapp</title>
<meta data-hid="description" name="description" content="My Nuxt portfolio" data-n-head="true">

You can find this example app in /chapter-4/nuxt-universal/view/html-head/ in our GitHub repository.

就这样。这就是 Nuxt 中应用模板和 HTML 头的全部内容。Nuxt 视图中的下一个向内层是布局,我们将在下一节中指导您如何创建自定义层。让我们开始吧。

创建自定义布局

布局是页面和组件的主干。您可能希望在应用中有多个不同的布局。当您使用npx create-nuxt-app脚手架工具安装应用时,会在/layouts/目录中自动生成一个名为default.vue的布局。与应用模板一样,您可以修改此默认布局或创建自己的自定义布局。

修改默认布局

默认布局始终用于没有特定或自定义布局的页面。如果您转到/layouts/目录并打开此布局,您应该会看到其中只有三行代码用于呈现页面组件:

// layouts/default.vue
<template>
  <nuxt/>
</template>

让我们修改此默认布局,如下所示:

// layouts/default.vue
<template>
  <div>
    <div>...add a navigation bar here...</div>
    <nuxt/>
  </div>
</template>

你应该看到你在那里添加的任何内容——例如,你应用中所有页面的导航栏。请注意,无论您是修改此布局还是创建新布局,请确保您拥有要 Nuxt 导入页面组件的<nuxt/>组件。让我们在下一节中探讨如何创建自定义布局。

创建新的自定义布局

有时,对于更复杂的应用,我们需要不止一个布局。对于某些页面,我们可能需要不同的布局。对于这种情况,您需要创建自定义布局。您可以使用.vue文件创建自定义布局,只需将它们放在/layouts/目录中即可。以以下为例:

// layouts/about.vue
<template>
  <div>
    <div>...add an about navigation bar here....</div>
    <nuxt/>
  </div>
</template>

然后,您可以使用页面组件中的layout属性将此自定义布局分配给该页面,如下所示:

// pages/about.vue
export default {
  layout: 'about'
  // OR
  layout (context) {
    return 'about'
  }
}

Nuxt 现在将使用此/layouts/about.vue文件作为此页面组件的基本布局。但是显示未知和无效路由的错误页面的布局如何?让我们在下一节中了解这是如何实现的。

创建自定义错误页

您安装的每个 Nuxt 应用都有一个默认错误页面,存储在 Nuxt 用来显示错误的目录下的@nuxt包中,例如 404、500 等等。您可以通过在/layouts/目录中添加一个error.vue文件来定制它。让我们通过以下步骤了解如何实现这一点:

  1. /layouts/目录中创建自定义错误页面,如下所示:
// layouts/error.vue
<template>
  <div>
    <h2 v-if="error.statusCode === 404">Page not found</h2>
    <h2 v-else>An error occurred</h2>
    <nuxt-link to="/">Home page</nuxt-link>
  </div>
</template>

<script>
export default {
  props: ['error']
}
</script>

请注意,错误页面是一个页面组件。一开始,它被放在/layouts/目录中而不是/pages/目录中,这似乎违反直觉,令人困惑。但是,即使它位于/layouts/目录中,也应将其视为一个页面。

  1. 与其他页面组件一样,您可以为此错误页面创建自定义布局,如下所示:
// layouts/layout-error.vue
<template>
  <div>
    <h1>Error!</h1>
    <nuxt/>
  </div>
</template>
  1. 然后,只需在错误页面的layout选项中添加layout-error
// layouts/error.vue
<script>
export default {
  layout: 'layout-error'
}
</script>
  1. 现在,如果导航到以下任何未知路由,Nuxt 将调用此自定义错误页面和自定义错误布局:
/company
/company/careers
/company/careers/london
/users/category/subject
/users/category/subject/type

You can find this 404 example in /chapter-4/nuxt-universal/view/custom-layouts/404/ in our GitHub repository.

就这样。这就是 Nuxt 中的所有布局。Nuxt 视图中的下一个向内层是页面,您将在下一节学习如何为应用创建自定义页面。所以,请继续阅读。

创建自定义页面

页面是 Nuxt 视图层的一部分,就像我们已经介绍过的应用模板、HTML 头部和布局一样。/pages/目录是存储页面的地方。您将花费大部分时间在此目录中为 Nuxt 应用创建页面。然而,创建页面并不是什么新鲜事——在上一节中,我们在/layouts/目录中创建了一个简单的错误页面,并且在学习如何为我们的应用创建自定义路由时创建了许多页面。因此,当您想要为特定路由创建自定义页面时,只需在/pages/目录中创建一个.vue文件;例如,我们可以创建以下页面:

pages/
--| index.vue
--| about.vue
--| contact.vue

但是,创建自定义页面需要的不仅仅是这些。我们需要知道 Nuxt 附带的页面上的属性和函数。尽管页面是 Nuxt 应用开发的重要组成部分,但在 Vue 应用开发中并不强调页面,页面与 Vue 组件有着密切的关系,其工作方式与组件略有不同。因此,为了创建一个页面并充分使用它,我们需要首先了解 Nuxt 中的页面是什么。让我们看看。

理解页面

在 Vue 中,页面本质上是一个组件。与标准 Vue 组件不同的是仅在 Nuxt 中添加的属性和函数。我们使用这些特殊属性和函数在呈现页面之前设置或获取数据,如下所示:

<template>
  <p>{{ message }}!</p>
</template>

<script>
export default {
  asyncData (context) {
    return { message: 'Hello World' }
  }
}
</script>

在前面的示例中,我们使用了一个名为asyncData的函数来设置消息键中的数据。此asyncData功能是您将看到的功能之一,并且经常在 Nuxt 应用中使用。让我们深入了解专门为 Nuxt 中的页面设计的属性和函数。

异步数据方法

asyncData方法是页面组件中最重要的功能。Nuxt 总是在启动页面组件之前调用此函数。这意味着每次请求页面时,都会在呈现页面之前首先调用此函数。它获取 Nuxt 上下文作为第一个参数,可以异步使用,如下所示:

<h1>{{ title }}</h1>

export default {
  async asyncData ({ params }) {
    let { data } = await axios.get(
    'https://jsonplaceholder.typicode.com/posts/' + params.id)
    return { title: data.title }
  }
}

在本例中,我们使用 ES6 destructuring assignment 语法来解压 Nuxt 上下文中打包的属性,这个特定属性是params。换句话说, { params }context.params的简写。我们还可以使用 destructuring assignment 语法来解压来自axios的异步结果中的data属性。请注意,如果您在页面组件的data函数中设置了数据,它将始终与asyncData中的数据合并。然后,合并后的数据可以在<template>块中使用。让我们创建一个简单的示例来演示asyncData如何与data函数合并:

<h1>{{ title }}</h1>

export default {
  data () {
    return { title: 'hello world!' }
  },
  asyncData (context) {
    return { title: 'hey nuxt!' }
  }
}

您有两组从dataasynData方法返回的数据对象,但前面代码的输出如下:

<h1>hey nuxt!</h1>

您可以看到,如果asyncData功能中的数据都使用相同的数据键,则asyncData功能中的数据将始终替换功能中的数据。另外,请注意,我们不能在asyncData方法中使用this关键字,因为该方法在页面组件启动之前被调用。所以您不能使用this.title = data.title这个方法来更新数据。我们将在第 8 章中介绍asyncData更多内容,添加服务器端框架

For more information about the destructuring assignment, visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment.

fetch 方法

fetch方法的工作原理与asyncData方法相同,只是在 Vue 生命周期挂钩之后称为——换句话说,在启动组件之后。与asyncData方法一样,它也可以异步使用;例如,您还可以使用它设置页面组件中的数据:

// pages/users/index.vue
<li v-for="user in users" v-bind:key="user.id">
  <nuxt-link :to="`users/${user.id}`">
    {{ user.name }}
  </nuxt-link>
</li>

import axios from 'axios'
export default {
  data () {
    return { users: [] }
  },
  async fetch () {
    let { data } = await axios.get
    ('https://jsonplaceholder.typicode.com/users')
    this.users = data
  }
}

请注意,必须使用data方法和fetch方法来设置数据。因为它是在页面组件启动后调用的,所以我们可以在data方法中使用this关键字访问对象。我们还可以使用此方法从页面组件设置 Vuex 存储中的数据,如下所示:

// pages/posts/index.vue
<li v-for="post in $store.state.posts" v-bind:key="post.id">
  <nuxt-link :to="`posts/${post.id}`">
    {{ post.title }}
  </nuxt-link>
</li>

import axios from 'axios'
export default {
  async fetch () {
    let { data } = await axios.get(
    'https://jsonplaceholder.typicode.com/posts')
    const { store } = this.$nuxt.context
    store.commit('setPosts', data)
  }
}

我们将在第 10添加一个 Vuex 存储中更多地介绍fetch方法。

You can find the preceding code in this section in /chapter-4/nuxt-universal/view/custom-pages/fecth-method/ in our GitHub repository.

For more information about the fetch method, please visit https://nuxtjs.org/api/pages-fetch and https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/.

头部法

head方法用于设置页面上的<meta>标记,我们在前面的创建自定义 HTML 标题部分中介绍了这一点。它也可以与/components/目录中的组件一起使用。

布局属性

layout键(或属性)用于从/layouts/目录中为页面指定布局,我们在前面的创建自定义布局部分中介绍了这一点。

负载特性

loading属性允许您禁用默认加载进度栏或在特定页面上设置自定义加载栏。我们在第 2 章Nuxt入门中简要介绍了它,因此我们知道可以在 Nuxt 配置文件中配置全局默认加载组件,如下所示:

// nuxt.config.js
export default {
  loading: {
    color: '000000'
  }
}

然而,由于我们在localhost上,并且不需要花费太多时间来处理数据,因此我们通常无法看到此加载条的运行。为了看到它的实际运行情况,让我们通过以下步骤延迟组件中数据的加载时间来演示此加载组件的工作方式和外观(但请注意,此演示不应在生产中进行):

  1. /pages/目录中创建一个index.vue页面,代码如下:
// pages/index.vue
<template>
  <div class="container">
    <p>Hello {{ name }}!</p>
    <NuxtLink to="/about">
      Go to /about
    </NuxtLink>
  </div>
</template>

<script>
export default {
  asyncData () {
    return new Promise((resolve) => {
      setTimeout(function () {
        resolve({ name: 'world' })
      }, 1000)
    })
  }
}
</script>
  1. /pages/目录中创建另一个名为about.vue的页面,代码如下:
// pages/about.vue
<template>
  <div class="container">
    <p>About Page</p>
    <NuxtLink to="/">
      Go to /
    </NuxtLink>
  </div>
</template>

<script>
export default {
  asyncData () {
    return new Promise((resolve) => {
      setTimeout(function () {
        resolve({})
      }, 1000)
    })
  }
}
</script>

在这两个页面中,我们使用setTimeout将数据响应延迟 1 秒。因此,在页面间导航时,在加载请求的页面之前,应该会看到页面顶部出现黑色加载栏。

You can find this example in /chapter-4/nuxt-universal/view/custom-pages/loading-page/ in our GitHub repository.

  1. 当然,我们可以通过在/components/目录中创建组件来创建自定义加载条或层。以以下为例:
// components/loading.vue
<template>
  <div v-if="loading" class="loading-page">
    <p>Loading...</p>
  </div>
</template>

<script>
export default {
  data () {
    return { loading: false }
  },
  methods: {
    start () { this.loading = true },
    finish () { this.loading = false },
  }
}
</script>

<style scoped>
.loading-page {
  position: fixed;
  //...
}
</style>

请注意,startfinish方法必须在自定义加载组件中公开,以便 Nuxt 可以在路由更改(调用start方法)和加载(调用finish方法)时调用您的组件并使用这些方法。

因此,在这个组件中,加载元素总是隐藏的,因为在data方法中loading属性默认设置为false。仅当loading属性在路线更改期间设置为true时,加载元素才可见。当loading属性设置为false时,在路线完成装载后,它将再次隐藏。

For more information about these and other available methods, please visit https://nuxtjs.org/api/configuration-loading.

  1. 在 Nuxt 配置文件的loading属性中包含前面自定义组件的路径:
// nuxt.config.js
export default {
  loading: '~/components/loading.vue'
}

You can find this example in /chapter-4/nuxt-universal/view/custom-pages/loading-global-custom/ in our GitHub repository.

  1. 我们还可以在特定页面上配置加载行为,如下所示:
// pages/about.vue
export default {
  loading: false
}
  1. 如果页面上loading键的值为false,则会自动停止调用this.$nuxt.$loading.finish()this.$nuxt.$loading.start()方法,这允许您在脚本中手动控制,如下所示:
// pages/about.vue
<span class="link" v-on:click="goToFinal">
  click here
</span>

export default {
  loading: false,
  mounted () {
    setTimeout(() => {
      this.$nuxt.$loading.finish()
    }, 5000)
  },
  methods: {
    goToFinal () {
      this.$nuxt.$loading.start()
      setTimeout(() => {
        this.$router.push('/final')
      }, 5000)
    }
  }
}
  1. 然后,在/pages/目录中创建final.vue页面:
// pages/final.vue
<template>
  <div class="container">
    <p>Final Page</p>
    <NuxtLink to="/">
      Go to /
    </NuxtLink>
  </div>
</template>

在本例中,您可以看到您使用this.$nuxt.$loading.finish()this.$nuxt.$loading.start()手动控制加载条。在mounted方法中,加载杆需要 5 秒钟才能完成。当您触发goToFinal方法时,加载条立即启动,将路线更改为/final需要 5 秒钟。

You can find this example in /chapter-4/nuxt-universal/view/custom-pages/loading-page/ in our GitHub repository.

过渡性质

transition属性用于指定页面的转换。可以将字符串、对象或函数与此键一起使用,如下所示:

// pages/about.vue
export default {
  transition: ''
  // or
  transition: {}
  // or
  transition (to, from) {}
}

我们将在本章后面的创建自定义转换部分深入介绍transition属性。

scrollToTop 属性

当您希望嵌套路由中的页面在呈现之前从顶部开始时,可以使用scrollToTop键。默认情况下,当您转到另一页时,Nuxt 滚动到顶部,但在嵌套路由中的子页上,Nuxt 与上一个子路由保持相同的滚动位置。因此,如果您想让 Nuxt 滚动到这些页面的顶部,请将scrollToTop设置为true,如下所示:

// pages/users/_id.vue
export default {
  scrollToTop: true
}

验证方法

validate方法是动态路由的验证器,我们之前已经在验证路由参数一节中介绍过。

中间件属性

middleware属性用于指定页面的中间件。分配的中间件将始终在呈现页面之前执行,如下所示:

// pages/secured.vue
export default {
  middleware: 'auth'
}

在本例中,auth是您将在/middleware/目录中创建的中间件的文件名,如下所示:

// middleware/auth.js
export default function ({ route }) {
  //...
}

我们将在第 11 章编写路由中间件和服务器中间件中深入介绍中间件。

就这样。您已经完成了 Nuxt 视图中的各个部分,从应用模板、HTML 标题和布局到页面。做得好!我们将在下一章介绍 Vue 组件。但是现在,我们应该关注的下一件事是在 Nuxt 中创建页面之间的自定义转换,因为转换和页面是密切相关的,就像刚才简要介绍的页面transition属性一样。那么,让我们继续本章的最后一个主题,在这里您将了解自定义转换的创建。

创建自定义转换

到目前为止,您已经为 Nuxt 应用创建了多个路由和页面,并添加了在页面之间切换时显示的加载栏。这已经是一款外观相当不错的应用了。但这并不是 Nuxt 的全部功能。您可以在页面之间添加更多令人惊叹的效果和过渡。这就是页面中的transition属性(例如,/pages/about.vue以及 Nuxt 配置文件中的pageTransitionlayoutTransition选项的作用。

我们可以通过 Nuxt 配置文件全局应用转换,或者在某些页面上具体应用。我们将引导您完成此主题。但是,要理解转换在 Nuxt 中的工作原理,我们应该首先了解它在 Vue 中的工作原理,然后才能了解如何在路由更改时在页面上实现它。让我们开始吧。**

**## 理解 Vue 转换

Vue 依赖于 CSS 转换,并使用<transition>Vue 组件包装 HTML 元素或 Vue 组件来添加 CSS 转换,如下所示:

<transition>
  <p>hello world</p>
</transition>

您可以看到这是多么简单–您可以像蛋糕一样用<transition>组件包装任何元素。发生这种情况时,Vue 将通过在适当的时间添加和删除以下 CSS 转换类,将其应用于该元素:

  • .v-enter.v-leave类定义了转换开始前元素的外观。
  • .v-enter-to.v-leave-to类是元素的“完成”状态。
  • .v-enter-active.v-leave-active类是元素的活动状态。

这些类是 CSS 转换发生的地方。例如,在 HTML 页面中执行的转换可以如下所示:

.element {
  opacity: 1;
  transition: opacity 300ms;
}
.element:hover {
  opacity: 0;
}

如果我们将前面的转换“转换”到 Vue 上下文中,我们将得到以下结果:

.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-leave,
.v-enter-to {
  opacity: 1;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 300ms;
}

我们可以直观地看到这些 Vue 转换类,如下图所示:

Reference source: https://vuejs.org/v2/guide/transitions.html

Vue 默认为转换类加上v-前缀,但如果您想更改此前缀,则只需使用<transition>组件上的name属性来指定名称,例如<transition name="fade">;然后,您可以“重构”CSS 转换类,如下所示:

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
.fade-leave,
.fade-enter-to {
  opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 300ms;
}

让我们通过以下步骤将上述转换应用于简单的 Vue 应用:

  1. 创建两个简单的路由,并用一个<transition>组件包装<router-view>组件,如下所示:
<div id="app">
  <p>
    <router-link to="/about">About</router-link>
    <router-link to="/contact">Contact</router-link>
  </p>
  <transition name="fade" mode="out-in">
    <router-view></router-view>
  </transition>
</div>
  1. 使用前面的fade-CSS 转换类添加一个<style>块:
<style type="text/css">
  .fade-enter,
  //...
</style>

当您在浏览器上运行应用时,您可以看到在路由之间切换时,路由组件淡入淡出需要 300 毫秒。

You can find this example in /chapter-4/vue/transitions/basic.html in our GitHub repository.

您可以看到,转换需要几个 CSS 类才能正常工作,但对于 Vue 应用来说,掌握它们并不困难。现在,让我们在下一节中了解如何在 Nuxt 中应用转换。

使用 pageTransition 属性进行转换

在 Nuxt 中,不再需要<transition>组件。默认情况下,它是为您添加的,因此您只需在任何特定页面 img/目录或块中创建转换。pageTransition`属性在 Nuxt 配置文件中用于设置页面转换的默认属性。Nuxt 中转换属性的默认值如下所示:

{
  name: 'page',
  mode: 'out-in'
}

因此,默认情况下,Nuxt 在转换类前面加上page-,而 Vue 使用v-作为前缀。默认转换模式在 Nuxt 中设置为out-in。让我们通过以下步骤为所有页面创建全局转换并为特定页面创建本地转换,来了解如何在 Nuxt 中完成转换:

  1. img/目录中创建一个transition.css`文件,并向其添加以下转换:
// img/css/transitions.css
.page-enter,
.page-leave-to {
  opacity: 0;
}
.page-leave,
.page-enter-to {
  opacity: 1;
}
.page-enter-active,
.page-leave-active {
  transition: opacity 300ms;
}
  1. 将前面 CSS 转换资源的路径添加到 Nuxt 配置文件:
// nuxt.config.js
export default {
  css: [
    'img/css/transitions.css'
  ]
}
  1. 请记住,默认前缀为page-,因此如果您想使用其他前缀,我们可以使用 Nuxt 配置文件中的pageTransition属性来更改该前缀:
// nuxt.config.js
export default {
  pageTransition: 'fade'
  // or
  pageTransition: {
    name: 'fade',
    mode: 'out-in'
  }
}
  1. 然后,在transitions.css中将所有默认类名中的前缀更改为fade,如下所示:
// img/css/transitions.css
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

此示例将在路由更改时在所有页面上全局应用转换。

  1. 但是,如果我们想要对特定页面应用不同的转换或覆盖页面中的全局转换,我们可以在该页面的transition属性中设置它,如下所示:
// pages/about.vue
export default {
  transition: {
    name: 'fade-about',
    mode: 'out-in'
  }
}
  1. 然后,为transitions.css中的fade-about创建 CSS 转换:
// img/css/transitions.css
.fade-about-enter,
.fade-about-leave-to {
  opacity: 0;
}
.fade-about-leave,
.fade-about-enter-to {
  opacity: 1;
}
.fade-about-enter-active,
.fade-about-leave-active {
  transition: opacity 3s;
}

在本例中,淡入淡出about页面需要 3 秒,而其余页面则需要 300 毫秒。

You can find this page-specific example and the global example in /chapter-4/nuxt-universal/transition/page-transition-property/ in our GitHub repository.

您可以再次看到,Nuxt 为您解除了一些重复的任务,并增加了为转换创建自定义前缀类名的灵活性。此外,您甚至可以在布局之间创建过渡!让我们在下一节中了解如何进行。

使用 layoutTransition 属性进行转换

CSS 转换不仅适用于页面组件,也适用于布局。此layoutTransition属性的默认值如下:

{
  name: 'layout',
  mode: 'out-in'
}

因此,布局转换类的前缀默认为layout,默认转换模式为out-in。让我们看看如何通过以下步骤为所有布局创建全局转换来完成转换:

  1. /layouts/目录中创建about.vueuser.vue布局,如下所示:
// layouts/about.vue
<template>
  <div>
    <p>About layout</p>
    //...
    <nuxt />
  </div>
</template>
  1. 将上述布局应用于/pages/目录中的about.vueusers.vue页面,如下所示:
// pages/about.vue
<script>
export default {
  layout: 'about'
}
</script>
  1. img/目录中创建一个transition.css`文件,并向其添加以下转换:
// img/css/transitions.css
.layout-enter, 
.layout-leave-to {
  opacity: 0;
}
.layout-leave,
.layout-enter-to {
  opacity: 1;
}
.layout-enter-active,
.layout-leave-active {
  transition: opacity .5s;
}
  1. 将前面 CSS 转换资源的路径添加到 Nuxt 配置文件:
// nuxt.config.js
export default {
  css: [
    'img/css/transitions.css'
  ]
}
  1. 默认前缀为layout-,但如果要使用其他前缀,可以使用 Nuxt 配置文件中的layoutTransition属性进行更改:
// nuxt.config.js
export default {
  layoutTransition: 'fade-layout'
  // or
  layoutTransition: {
    name: 'fade-layout',
    mode: 'out-in'
  }
}
  1. transitions.css中将所有默认类名中的前缀更改为fade-layout,如下所示:
// img/css/transitions.css
.fade-layout-enter,
.fade-layout-leave-to {
  opacity: 0;
}

在本例中,整个布局(包括导航)淡入淡出需要 0.5 秒。当您在使用不同布局的页面之间导航时,您将看到这种转换,而不是在使用相同布局的页面之间导航;例如,如果您在//contact之间导航,则不会得到前面的布局转换,因为它们都使用相同的布局,即/layouts/default.vue

You can find this example in /chapter-4/nuxt-universal/transition/layout-transition-property/ in our GitHub repository.

再一次,您可以看到为布局创建转换非常容易,并且您可以自定义它们的前缀类名,就像页面转换一样。除了使用 CSS 转换来转换页面和布局外,我们还可以使用 CSS 动画。那么,让我们在下一节中了解如何进行。

使用 CSS 动画进行转换

CSS 转换是仅在两种状态之间执行的动画:开始和结束。但是,当您需要更多中间状态时,应该改用 CSS 动画,以便通过添加多个在开始和结束状态之间具有不同百分比的关键帧来获得更多控制。以以下为例:

@keyframes example {
  0% { // 1st keyframe or start state.
    background-color: red;
  }
  25% { // 2nd keyframe.
    background-color: yellow;
  }
  50% { // 3rd keyframe.
    background-color: blue;
  }
  100% { // 4th keyframe end state.
    background-color: green;
  }
}

0%是动画的开始状态,100%是动画的结束状态。您可以通过添加增量百分比在这两个状态之间添加更多中间状态–例如,10%20%30%等等。但是,CSS 转换没有添加这些关键帧的能力。所以,我们可以说 CSS 转换是 CSS 动画的一种简单形式。

因为 CSS 转换实际上是 CSS 动画,所以我们可以像在 Vue/Nuxt 应用中应用 CSS 转换一样应用 CSS 动画。让我们通过以下步骤了解如何:

  1. 将以下 CSS 动画代码添加到transitions.css文件中,就像您在上一节中所做的那样:
// img/css/transitions.css
.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
  1. 将 Nuxt 配置文件中的全局默认page-前缀更改为bounce-
// nuxt.config.js
export default {
  pageTransition: 'bounce'
}

添加上述代码后,请刷新浏览器,在页面之间切换时,您将看到页面跳入跳出。

You can find this example in /chapter-4/nuxt-universal/transition/css-animations/ in our GitHub repository.

根据您想要制作动画的复杂程度和详细程度,以及 CSS 动画技能的水平,您可以为页面和布局创建非常出色的过渡。您只需专注于编写代码并通过 Nuxt 配置文件进行注册,然后 Nuxt 将负责在适当的时间添加和删除 CSS 动画类的其余工作。但是 JavaScript 呢?我们可以使用 jQuery 或任何其他 JavaScript 动画库来创建动画来转换页面和布局吗?答案是可以的。让我们在下一节中了解如何进行。

使用 JavaScript 挂钩进行转换

除了使用 CSS 进行转换外,您还可以通过在 Vue 应用的<transition>组件中添加以下挂钩来使用 JavaScript 进行转换:

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"
  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  //..
</transition>

Note that you also can declare the hook without adding v-on at the beginning. So, writing the hook as :before-enter is the same as writing v-on:before-enter.

然后,在 JavaScript 方面,methods属性中应该有以下默认方法来对应前面的钩子:

methods: {
  beforeEnter (el) { ... },
  enter (el, done) {
    // ...
    done()
  },
  afterEnter (el) { ... },
  enterCancelled (el) { ... },
  beforeLeave (el) { ... },
  leave (el, done) {
    // ...
    done()
  },
  afterLeave (el) { ... },
  leaveCancelled (el) { ... }
}

您可以单独使用这些 JavaScript 挂钩,也可以与 CSS 转换一起使用。如果您单独使用它们,done回调必须在enterleave钩子(方法)中使用,否则这两个方法将同步运行,并且您尝试应用此方法的动画或转换将立即结束。此外,如果它们单独使用,您也应该在<transition>包装器上使用v-bind:css="false",这样 Vue 就可以安全地忽略您的元素,以防您的应用中也有 CSS 转换,但它正用于其他元素。让我们通过以下步骤使用这些 JavaScript 挂钩制作一个简单的 Vue 应用:

  1. 将以下 CDN 链接添加到 HTML<head>块:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  1. 将应用标记和带有挂钩的<transition>组件添加到<body>块:
<div id="app">
  <p>
    <router-link to="/about">About</router-link>
    <router-link to="/contact">Contact</router-link>
  </p>
  <transition
    appear
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-on:after-leave="afterLeave">
    <router-view></router-view>
  </transition>
</div>
  1. <script>块按此操作,并用以下方法与前面的挂钩协调:
const app = new Vue({
  name: 'App',
  methods: {
    beforeEnter (el) { $(el).hide() },
    enter (el, done) {
      $(el).fadeTo('slow', 1)
      done()
    },
    leave (el, done) {
      $(el).fadeTo('slow', 0, function () {
        $(el).hide()
      })
      done()
    },
    afterLeave (el) { $(el).hide() }
  }
}).$mount('#app')

在本例中,我们使用 jQuery 中的fadeTo方法来控制转换,而不是使用纯 CSS。在它们之间切换时,您应该看到路由组件淡入淡出,就像.v-enter.v-leaveCSS 转换一样。

You can find this example in /chapter-4/vue/transition/js-hooks.html in our GitHub repository.

在 Nuxt 中,我们不需要为<transition>组件定义 JavaScript 钩子,只需要为 Nuxt 配置文件定义pageTransition中的 JavaScript 方法,为/pages/目录中的任何.vue文件定义transition中的 JavaScript 方法。让我们通过以下步骤在 Nuxt 应用中创建一个快速示例:

  1. 通过 npm 在终端上安装 jQuery:
$ npm i jquery
  1. 由于我们在 Nuxt 配置文件和其他页面中使用 jQuery,我们可以通过 Nuxt 配置文件中的 webpack 全局加载 jQuery
// nuxt.config.js
import webpack from 'webpack'

export default {
  build: {
    plugins: [
      new webpack.ProvidePlugin({
        $: "jquery"
      })
    ]
  }
}
  1. 在 Nuxt 配置文件的pageTransition选项中使用 jQuery 创建全局转换:
// nuxt.config.js
export default {
  pageTransition: {
    mode: 'out-in',
    css: false,
    beforeEnter: el => { $(el).hide() },
    enter: (el, done) => {
      $(el).fadeTo(1000, 1)
      done()
    },
    //...
  }
}

此示例将在路由更改时在所有页面上全局应用转换。此外,我们还通过将css选项设置为false来关闭 CSS 转换。

Note that we write the JavaScript functions with object keys as an alternative to associating with the attribute hooks in the transition component.

  1. /pages/目录中创建一个about.vue页面,并通过about.vue页面上的transition属性应用不同的转换来覆盖前面的全局转换:
// pages/about.vue
export default {
  transition: {
    mode: 'out-in',
    css: false,
    beforeEnter: el => { $(el).hide() },
    enter: (el, done) => {
      $(el).fadeTo(3000, 1)
      done()
    },
    //...
  }
}

因此,在本例中,在这个特定页面上进行转换需要 3 秒钟,而其他页面则需要 1 秒钟。

请注意,如果未在 Nuxt 配置文件中加载 jQuery,则必须将其导入到.vue页面;例如,假设您只想在此特定页面上设置转换:

// pages/about.vue
import $ from 'jquery'

export default {
  transition: {
    beforeEnter (el) { $(el).hide() },
    //...
  }
}

代码就位后,请刷新浏览器,当页面之间的路由发生更改时,您应该会看到页面淡入淡出,就像 Vue 应用一样。

You can find this example in /chapter-4/nuxt-universal/transition/js-hooks/ in our GitHub repository.

做得好;您已经完成了有关在 Nuxt 中创建转换的部分!Nuxt 可以在应用中看到动画,这是另一种在应用中编写动画的好方法。但是在结束这一章之前,让我们来看看我们在过渡期间看到的过渡模式。那么,让我们看看它们的用途。

Note that even though jQuery is not encouraged these days, it is used occasionally in this book because it is a dependency of Foundation, which you learned about in the previous chapter. So, we will be reusing it sometimes. Alternatively, you can use Anime.js to make JavaScript animations. For more information about this library, please visit https://animejs.com/.

理解过渡模式

您可能想知道什么是mode="out-in"(在 Vue 中)或mode: 'out-in'(在 Nuxt 中)——例如,在我们以前的 Vue 应用中,其中包含<div>about</div><div>contact</div>组件。它们之所以存在,是因为<div>about</div><div>contact</div>之间的转换是同时呈现的。这是<transition>的默认行为:同时进入和离开。但有时,您可能不希望同时进行这种转换,因此 Vue 提供了具有以下转换模式的解决方案:

  • in-out模式

此模式用于让新元素先进入,直到其转换完成,然后当前元素将移出。

  • out-in模式

此模式用于让当前元素先移出,直到其转换完成,然后新元素将移入。

因此,您可以通过以下方式使用这些模式:

  • 在 Vue.js 应用中,按如下方式使用它们:
<transition name="fade" mode="out-in">
  //...
</transition>
  • 在 Nuxt 应用中,按如下方式使用它们:
export default {
  transition: {
    name: 'fade',
    mode: 'out-in'
  }
}
  • 在 JavaScript 挂钩中,按如下方式使用它们:
export default {
  methods: {
    enter (el, done) {
      done() // equivalent to mode="out-in"
    },
    leave (el, done) {
      done() // equivalent to mode="out-in"
    }
  }
}

在为我们的 Nuxt/Vue 应用创建自定义转换这一主题上,我们取得了长足的进步。您现在可以根据本章所学内容制作一些像样的过渡和动画。因此,由于本书篇幅有限,我们将不再详细介绍这个主题,但有关 Vue 过渡和动画的更多信息和进一步阅读,请访问https://vuejs.org/v2/guide/transitions.html 。现在,让我们总结一下您在本章学到的知识!

总结

在本章中,您了解了 Nuxt 中的页面是什么,以及如何为应用创建不同类型的路由。您学习了如何自定义默认应用模板和布局,以及如何创建新的布局和 404 页面。您学习了如何使用 CSS 转换和动画,以及 JavaScript 挂钩和方法,使应用页面之间的转换有趣。如果您从一开始就遵循指南,那么现在您应该能够交付一个布局美观的小项目。您可以在我们的 GitHub 存储库的/chapter-4/nuxt-universal/sample-website/中找到一个网站示例,它使用了我们在本章和前几章中所学到的知识。

在下一章中,我们将探索/components/目录。通过更详细地了解 Vue 组件,您将了解如何在 Nuxt 应用中使用它来优化本章介绍的布局和页面,包括从页面和布局组件向它们传递数据、创建单文件 Vue 组件、注册全局和本地 Vue 组件等。此外,您还将学习如何使用 mixin 编写可重用代码,使用《Vue 样式指南》中的命名约定定义组件名称,以便对组件进行组织和标准化,以便将来更好地维护。所有这些都值得了解和探索。那么,让我们开始吧。*