创建自定义视图
你创建的每个自定义路由最终都会呈现为一个 “页面”,该页面包含了我们希望在前端展示的所有 HTML
标记和内容。从软件架构的角度来看,这些 HTML
标记和内容,包括元信息、图片和字体,构成了你应用程序的视图或表现层。在 Nuxt
中,我们可以轻松地创建和自定义我们的视图。让我们一起探索构成 Nuxt
视图的要素以及如何自定义它。
理解 Nuxt 视图
Nuxt
中的视图结构由 应用模板、HTML head、布局 和 页面层 组成。你可以使用它们为你的应用路由创建视图。在一个更复杂的应用中,你会从 API
获取数据来填充它们,而在一个简单的应用中,你可以直接将虚拟数据手动嵌入到其中。我们将在接下来的章节中逐步介绍这些层。在深入了解之前,请花一点时间研究下面的图表,它将为你提供 Nuxt
视图的完整概览:

你可以看到,“文档 - HTML 文件” 是 Nuxt
视图的最外层,其次是 “布局”、“页面” 以及可选的 “页面子组件” 和 “Vue 组件” 层。“文档 - 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>
重启你的应用程序,你应该会看到你的自定义 app HTML
模板已经替换了 Nuxt
的默认模板。
你可以在我们的 |
最接近 HTML
文档(即 <html>
元素)的下一层是 HTML
的头部,即 <head>
元素,它包含重要的元信息以及页面的脚本和样式。我们不会直接在 app
模板中添加或自定义这些数据,而是在 Nuxt
配置文件和 /pages/
目录下的文件中进行。所以,让我们在下一节中了解如何操作。
创建自定义 HTML head
HTML
的 <head>
元素包含 <title>
, <style>
, <link>
和 <meta>
元素。手动添加这些元素可能是一项繁琐的任务。因此,Nuxt
在你的应用程序中为你处理这些元素。在第二章 “Nuxt 入门” 中,你了解到 Nuxt
会从 JavaScript
对象(用花括号 {}
括起来)中的数据为你生成这些元素,这些数据写在 Nuxt
配置文件中,如下所示:
// nuxt.config.js
export default {
head: {
title: '默认标题',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'parent' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
}
}
在本主题中,我们对 Nuxt
配置文件中的 meta
块以及 /pages/
目录中的页面感兴趣。Nuxt
使用 Vue Meta
插件来管理这些 meta
属性。因此,要理解它在 Nuxt
中是如何工作的,我们首先应该理解 Vue Meta
在传统的 Vue
应用程序中是如何工作的。
Vue Meta 简介
Vue Meta
是一个 Vue
插件,用于在 Vue
中管理和创建具有内置响应式的 HTML
元数据。你只需要在任何 Vue
组件中添加 metaInfo
这个特殊的属性,它就会自动渲染成 HTML
的 meta
标签,如下所示:
// 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">
有关 |
你可以看到,你所需要做的只是在一个 JavaScript
对象中提供元数据。现在,让我们安装它并学习如何为 Vue
应用程序配置它。
安装 Vue Meta
像所有其他的 Vue
插件一样,你可以通过以下步骤安装 Vue Meta
并将其连接到你的 Vue
应用程序:
-
通过
npm
安装Vue Meta
:$ npm i vue-meta
或者,你也可以通过 CDN 使用
<script>
元素安装它,如下所示:<script src="https://unpkg.com/vue-meta@1.5.8/lib/vue-meta.js"></script>
-
如果你正在编写
ES6 JavaScript
应用程序,请在你的主应用程序文件中导入Vue
和Vue Router
以及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({ //... })
-
然后,你可以在任何
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
方法从异步数据中注入 meta
信息,而不是使用 metaInfo
属性。你甚至可以使用 titleTemplate
选项为你的页面标题添加一个模板,就像前面的例子一样。接下来,我们将创建一个使用这个插件的简单 Vue
应用程序,以便你可以更全面地了解如何使用它。
在 Vue 应用中使用 Vue Meta 创建元数据
通常情况下,我们可以在一个单独的 HTML
页面上启动并运行一个 Vue
应用。让我们开始吧:
-
在
<head>
标签中包含CDN
链接:<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>
-
在
<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...' } ] } }
-
然后,在根实例中添加默认的元数据:
const app = new Vue({ metaInfo: { title: 'Nuxt', titleTemplate: '%s | My Awesome Webapp', meta: [ { vmid: 'description', name: 'description', content: 'My Nuxt portfolio' } ] }, router }).$mount('#app')
请注意,我们可以通过简单地在子组件的
titleTemplate
选项中添加null
来覆盖组件中的默认元模板,就像前面的About
组件一样。你可以在我们的
GitHub
仓库的/chapter-4/vue/vuemeta/basic.html
中找到这个示例应用。
在这个例子中,由于我们没有使用 axios
异步获取数据,我们可以直接使用 metaInfo
属性,而不是使用 metaInfo
方法来注入包含异步数据的元信息。然后,当你导航到你刚刚创建的路由时,你将在浏览器中看到页面标题和元信息的变化。在 Vue
应用中使用这个插件非常容易,不是吗?现在,我们将在下一节中了解它在 Nuxt
应用中是如何工作的。
自定义 Nuxt 应用中的默认 meta 标签
在 Nuxt
应用中创建和自定义元信息更加简单,因为 Nuxt
默认集成了 Vue Meta
。这意味着你不需要像在 Vue
应用中那样安装它。你只需要在 Nuxt
配置文件中使用 head
属性来定义应用的默认 <meta>
标签,如下所示:
// nuxt.config.js
head: {
title: 'Nuxt',
titleTemplate: '%s | 我的超棒 Web 应用',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '我的 Nuxt 作品集' }
]
}
然而,Nuxt
和 Vue
应用的区别在于,在 Nuxt
中必须使用 hid
键,而在 Vue
中使用 vmid
。为了防止在子组件中定义元标签时出现重复,你应该始终为你的元素使用 hid
。另外,请注意 metaInfo
键只在 Vue
中使用,而在 Nuxt
中使用 title
键来添加我们的元信息。
这就是为你的 Nuxt
应用添加和自定义标题和元标签的方法。然而,它们是全局添加的,这意味着它们会应用到你的应用中的所有页面。那么,如何在 Nuxt
配置文件中专门为某个页面添加元信息并覆盖全局的元信息呢?让我们在下一节中找到答案。
为 Nuxt 页面创建自定义 meta 标签
如果您想为特定页面添加自定义元标签或覆盖 Nuxt
配置文件中的默认元标签,只需在该特定页面上直接使用 head
方法,该方法将返回一个包含 title
和 meta
选项数据的 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">
您可以在我们 |
就是这样。这就是关于 Nuxt
中应用程序模板和 HTML head
的全部内容。Nuxt
视图的下一个内部层是布局,我们将在下一节中指导您如何创建自定义布局。让我们开始吧。
创建自定义布局
布局是你页面和组件的骨干。你可能希望在你的应用中拥有多种不同的布局。有一个名为 default.vue
的布局,当你使用 npx create-nuxt-app
脚手架工具安装你的应用时,它会自动在 /layouts/
目录下生成。就像应用模板一样,你可以修改这个默认布局或者创建你自己的自定义布局。
修改默认布局
默认布局始终用于没有特定或自定义布局的页面。如果你前往 /layouts/
目录并打开此布局,你应该看到其中只有三行代码用于渲染你的页面组件:
// layouts/default.vue
<template>
<nuxt/>
</template>
让我们修改这个默认布局,如下所示:
// layouts/default.vue
<template>
<div>
<div>...在此处添加导航栏...</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'
// 或者
layout (context) {
return 'about'
}
}
现在,Nuxt
将使用 /layouts/about.vue
文件作为此页面组件的基础布局。但是,对于显示未知和无效路由的错误页面的布局又该如何处理呢?让我们在下一节中了解它是如何实现的。
创建自定义错误页面
每个你安装的 Nuxt
应用都带有一个默认的错误页面,该页面存储在 @nuxt
包的 /node_modules/
目录下,Nuxt
使用它来显示错误,例如 404
、500
等等。你可以通过在 /layouts/
目录下添加一个 error.vue
文件来定制它。让我们通过以下步骤来了解如何实现这一点:
-
在
/layouts/
目录下创建一个自定义错误页面,如下所示:// layouts/error.vue <template> <div> <h2 v-if="error.statusCode === 404">页面未找到</h2> <h2 v-else>发生了一个错误</h2> <nuxt-link to="/">首页</nuxt-link> </div> </template> <script> export default { props: ['error'] } </script>
请注意,错误页面是一个页面组件。起初,这似乎有些违反直觉和令人困惑,因为它被放置在
/layouts/
目录下而不是/pages/
目录下。然而,即使它位于/layouts/
目录下,也应该将其视为一个页面。 -
就像其他的页面组件一样,你可以为这个错误页面创建一个自定义布局,如下所示:
// layouts/layout-error.vue <template> <div> <h1>Error!</h1> <nuxt/> </div> </template>
-
然后,只需将
layout-error
添加到错误页面的layout
选项中:// layouts/error.vue <script> export default { layout: 'layout-error' } </script>
-
现在,如果你导航到以下任何未知路由,
Nuxt
将调用这个自定义错误页面和自定义错误布局:-
/company
-
/company/careers
-
/company/careers/london
-
/users/category/subject
-
/users/category/subject/type
-
你可以在我们的 |
就是这样。以上就是关于 Nuxt
中布局的全部内容。Nuxt
视图的下一个内部层是页面,你将在下一节学习如何为你的应用创建自定义页面。所以,请继续阅读。
创建自定义页面
页面是 Nuxt
视图层的一部分,就像我们已经介绍过的应用程序模板、HTML
head
和布局一样。/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
的函数来设置 message
键中的数据。asyncData
函数是你在 Nuxt
应用中会看到并经常使用的函数之一。让我们深入了解那些专门为 Nuxt
页面设计的属性和函数。
asyncData 方法
asyncData
方法是页面组件中最重要的函数。Nuxt
总是在初始化页面组件之前调用此函数。这意味着每次你请求一个页面时,这个函数都会在页面渲染之前首先被调用。它将 Nuxt
上下文作为第一个参数,并且可以异步使用,如下所示:
<h1>{{ title }}</h1>
<script>
import axios from 'axios'
export default {
async asyncData ({ params }) {
let { data } = await axios.get(
'https://jsonplaceholder.typicode.com/posts/' + params.id
)
return { title: data.title }
}
}
</script>
在这个例子中,我们使用 ES6
的解构赋值语法来解包 Nuxt
上下文中打包的属性,而这个特定的属性是 params
。换句话说,{ params }
是 context.params
的简写。我们也可以使用解构赋值语法来解包来自 axios
异步结果中的 data
属性。请注意,如果你在页面组件的 data
函数中设置了 data
,它将始终与来自 asyncData
的数据合并。然后,合并后的数据可以在 <template>
块中使用。让我们创建一个简单的例子来演示 asyncData
如何与 data
函数合并:
<h1>{{ title }}</h1>
<script>
export default {
data () {
return { title: 'hello world!' }
},
async asyncData (context) {
return { title: 'hey nuxt!' }
}
}
</script>
你看到了两个从 data
和 asyncData
方法返回的数据对象,但是你将得到的上述代码的输出是:
<h1>hey nuxt!</h1>
你可以看到,如果 asyncData
函数和 data
函数都使用相同的键,那么来自 asyncData
函数的数据将始终替换 data
函数中的数据。另请注意,我们不能在 asyncData
方法中使用 this
关键字,因为这个方法是在页面组件初始化之前被调用的。因此,你不能使用 this.title = data.title
来更新数据。我们将在第八章 “添加服务端框架” 中更详细地介绍 asyncData
。
fetch 方法
fetch
方法的工作方式与 asyncData
方法相同,不同之处在于它在 Vue
的 created
生命周期钩子之后被调用——换句话说,在组件初始化之后。与 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>
<script>
import axios from 'axios'
export default {
data () {
return { users: [] }
},
async fetch () {
let { data } = await axios.get(
'https://jsonplaceholder.typicode.com/users'
)
this.users = data
}
}
</script>
请注意,data
方法必须与 fetch
方法一起使用才能设置数据。由于它在页面组件初始化之后被调用,我们可以使用 this
关键字来访问 data
方法中的对象。我们也可以使用此方法从页面组件设置 Vuex store
中的数据,如下所示:
// 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>
<script>
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)
}
}
</script>
我们将在第 10 章 “添加 Vuex Store” 中更详细地介绍 fetch
方法和 Vuex store
。
你可以在我们的 |
loading 属性
加载属性允许您禁用默认的加载进度条或在特定页面上设置自定义加载条。我们在第二章 “Nuxt.js 入门” 中简要介绍过它,所以我们知道可以在 Nuxt
配置文件中配置全局默认的加载组件,如下所示:
// nuxt.config.js
export default {
loading: {
color: '000000'
}
}
然而,由于我们是在 localhost
上进行开发,并且通常不需要太多时间来处理数据,我们通常无法看到这个加载条的实际效果。为了演示这个加载组件的工作方式和外观,让我们通过延迟组件中数据的加载时间来演示一下(但请注意,这个演示不应该在生产环境中使用):
-
在
/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>
-
在
/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
秒。因此,当在页面之间导航时,您应该在请求的页面加载之前看到黑色的加载条出现在页面顶部。您可以在我们的
GitHub
仓库的/chapter-4/nuxtuniversal/view/custom-pages/loading-page/
中找到这个示例。 -
当然,我们可以通过在
/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>
请注意,自定义加载组件中必须暴露
start
和finish
方法,以便Nuxt
可以在路由更改时(调用start
方法)和加载完成时(调用finish
方法)调用您的组件并使用这些方法。因此,在这个组件中,加载元素始终是隐藏的,因为
loading
属性在data
方法中默认设置为false
。只有在路由更改期间loading
属性设置为true
时,加载元素才会变得可见。然后在路由加载完成后,loading
属性再次设置为false
,加载元素又会隐藏起来。有关这些方法和其他可用方法的更多信息,请访问 https://nuxtjs.org/api/configuration-loading 。
-
将上述自定义组件的路径包含在
Nuxt
配置文件的loading
属性中:// nuxt.config.js export default { loading: '~/components/loading.vue' }
您可以在我们的
GitHub
仓库的/chapter-4/nuxtuniversal/view/custom-pages/loading-global-custom/
中找到这个示例。 -
我们还可以配置特定页面的加载行为,如下所示:
// pages/about.vue export default { loading: false }
-
如果页面上的
loading
键的值为false
,它将停止自动调用$nuxt.$loading.finish()
和$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) } } }
-
然后,在
/pages/
目录下创建final.vue
页面:// pages/final.vue <template> <div class="container"> <p>Final Page</p> <NuxtLink to="/"> Go to / </NuxtLink> </div> </template>
在这个示例中,您可以看到可以通过 $nuxt.$loading.finish()
和 $nuxt.$loading.start()
手动控制加载条。在 mounted
方法中,加载条需要 5
秒钟才能完成。当您触发 goToFinal
方法时,加载条会立即启动,并且需要 5
秒钟才能将路由更改为 /final
。
您可以在我们的 |
transition 属性
transition
属性用于指定页面的过渡效果。你可以使用字符串、对象或函数作为这个键的值,如下所示:
// pages/about.vue
export default {
transition: ''
// 或者
transition: {}
// 或者
transition (to, from) {}
}
我们将在本章后面的 “创建自定义过渡效果” 部分深入探讨 transition
属性。
scrollToTop 属性
当您希望嵌套路由中的页面在渲染之前从顶部开始时,可以使用 scrollToTop
键。默认情况下,当您导航到另一个页面时,Nuxt
会滚动到顶部,但在嵌套路由的子页面中,Nuxt
会保持与前一个子路由相同的滚动位置。因此,如果您想告诉 Nuxt
为这些页面滚动到顶部,请将 scrollToTop
设置为 true
,如下所示:
// pages/users/_id.vue
export default {
scrollToTop: true
}
middleware 属性
middleware
属性用于为页面指定中间件。分配的中间件总是在页面渲染之前执行,如下所示:
// pages/secured.vue
export default {
middleware: 'auth'
}
在这个例子中,auth
是你将在 /middleware/
目录下创建的中间件的文件名,如下所示:
// middleware/auth.js
export default function ({ route }) {
//...
}
我们将在第 11 章 “编写路由中间件和服务端中间件” 中深入介绍中间件。
就这样,你已经完成了关于 Nuxt
视图的各个部分,从应用模板、HTML head
和布局到页面。做得好!我们将在下一章介绍 Vue
组件。但是现在,我们接下来应该看一下在 Nuxt
中创建自定义页面过渡效果,因为过渡效果和页面是紧密相关的,就像你已经被简要介绍过的 page Transition
属性一样。所以,让我们继续本章的最后一个主题,你将学习如何创建自定义过渡效果。