安装 Nuxt
NuxtJS Web 开发实用指南(全)
原文:zh.annas-archive.org/md5/95454EEF6B1A13DFE0FAD028BE716A19
译者:飞龙
协议:CC BY-NC-SA 4.0
前言
Nuxt.js(本书中将其称为 Nuxt)是建立在 Vue.js 之上的渐进式 Web 框架(本书中将其称为 Vue)用于服务器端渲染(SSR)。使用 Nuxt 和 Vue,构建通用和静态生成的应用程序比以往任何时候都更容易。本书将帮助您快速了解 Nuxt 的基础知识以及如何将其与最新版本的 Vue 集成,使您能够使用 Nuxt 和 Vue.js 构建整个项目,包括身份验证、测试和部署。您将探索 Nuxt 的目录结构,并通过使用 Nuxt 的页面、视图、路由和 Vue 组件以及编写插件、模块、Vuex 存储和中间件来创建您的 Nuxt 项目。此外,您还将学习如何使用 Koa.js(本书中将其称为 Koa)、PHP 标准建议(PSRs)、MongoDB 和 MySQL 从头开始创建 Node.js 和 PHP API 或数据平台,以及使用 WordPress 作为无头 CMS 和 REST API。您还将使用 Keystone.js 作为 GraphQL API 来补充 Nuxt。您将学习如何使用 Socket.IO 和 RethinkDB 创建实时 Nuxt 应用程序和 API,最后使用 Nuxt 从远程 API 生成静态站点并流式传输资源(图像和视频),无论是 REST API 还是 GraphQL API。
本书适合对象
这本书适用于任何想要构建服务器端渲染的 Vue.js 应用程序的 JavaScript 或全栈开发人员。对 Vue.js 框架的基本理解将有助于理解本书涵盖的关键概念。
本书内容
第一章《介绍 Nuxt》是您将了解 Nuxt 的主要特点的地方。您将了解今天有哪些类型的 Web 应用程序,以及 Nuxt 属于哪些类别。然后,您将在接下来的章节中了解您可以使用 Nuxt 做什么。
第二章《开始使用 Nuxt》是您将安装 Nuxt 的地方,使用脚手架工具,或者从头开始创建您的第一个基本 Nuxt 应用程序。您将了解 Nuxt 项目中的默认目录结构,配置 Nuxt 以适应您的项目,并理解资源服务。
第三章《添加 UI 框架》是您将添加自定义 UI 框架,例如 Zurb Foundation,Motion UI,Less CSS 等等,以使您在 Nuxt 中的 UI 开发更加轻松和有趣。
第四章,“添加视图、路由和过渡”,是您将在 Nuxt 应用程序中创建导航路由、自定义页面、布局和模板的地方。您将学习如何添加过渡和动画,创建自定义错误页面,自定义全局 meta 标签,并为单个页面添加特定标签。
第五章,“添加 Vue 组件”,是您将在 Nuxt 应用程序中添加 Vue 组件的地方。您将学习如何创建全局和局部组件并重用它们,编写基本和全局 mixin,并定义符合命名约定的组件名称。
第六章,“编写插件和模块”,是您将在 Nuxt 应用程序中创建和添加插件、模块和模块片段的地方。您将学习如何创建 Vue 插件并将其安装在您的 Nuxt 项目中,编写全局函数并注册它们。
第七章,“添加 Vue 表单”,是您将使用v-model和v-bind创建表单的地方,验证表单元素并通过使用修饰符进行动态值绑定。您还将学习如何使用 Vue 插件 VeeValidate,使前端验证变得更加简单。
第八章,“添加服务器端框架”,是您将使用 Koa 作为服务器端框架创建 API 来补充您的 Nuxt 应用程序的地方。您将学习如何安装 Koa 及其必要的 Node.js 包以创建一个完全可用的 API,并将其与您的 Nuxt 应用程序集成。此外,您还将学习如何在 Nuxt 中使用异步数据从 Koa API 获取数据,通过异步数据访问 Nuxt 上下文,监听查询变化,处理错误,并使用 Axios 作为请求 API 数据的 HTTP 客户端。
第九章,“添加服务器端数据库”,是您将使用 MongoDB 管理 Nuxt 应用程序的数据库的地方。您将学习如何安装 MongoDB,编写基本的 MongoDB 查询,向 MongoDB 数据库添加一些虚拟数据,将 MongoDB 与上一章的 Koa API 集成,然后从 Nuxt 应用程序中获取数据。
第十章,添加 Vuex 存储,是您将使用 Vuex 管理和集中存储 Nuxt 应用程序数据的地方。您将了解 Vuex 架构,使用存储的变异和操作方法来改变存储数据,当存储变得更大时如何以模块化的方式构建您的存储程序,以及如何在 Vuex 存储中处理表单。
第十一章,编写路由中间件和服务器中间件,是您将在 Nuxt 应用程序中创建路由中间件和服务器中间件的地方。您将学习如何使用 Vue Router 创建中间件,使用 Vue CLI 创建 Vue 应用程序,并使用 Express.js(本书中称为 Express)、Koa 和 Connect.js(本书中称为 Connect)作为服务器中间件。
第十二章,创建用户登录和 API 身份验证,是您将在 Nuxt 应用程序中为受限页面添加身份验证的地方,使用会话、cookies、JSON Web Tokens(JWTs)、Google OAuth 以及您在上一章中学到的路由中间件。您将学习如何使用 JWT 在后端进行身份验证,在 Nuxt 应用程序中在客户端和服务器端使用 cookies(前端身份验证),以及在后端和前端身份验证中添加 Google OAuth。
第十三章,编写端到端测试,是您将使用 AVA、jsdom 和 Nightwatch.js 创建端到端测试的地方。您将学习如何安装这些工具,设置测试环境,并为上一章中 Nuxt 应用程序的页面编写测试。
第十四章,使用 Linter、格式化程序和部署命令,是您将使用 ESLint、Prettier 和 StandardJS 来保持代码清洁、可读和格式化的地方。您将学习如何安装和配置这些工具以满足您的需求,并在 Nuxt 应用程序中集成不同的 linter。最后,您将学习如何使用 Nuxt 命令部署您的 Nuxt 应用程序,并了解发布应用程序的托管服务。
第十五章,使用 Nuxt 创建 SPA,您将学习如何在 Nuxt 中开发单页应用程序(SPA),了解 Nuxt 中 SPA 与经典 SPA 的区别,并生成静态 SPA 以部署到静态托管服务器 GitHub Pages。
第十六章,为 Nuxt 创建一个与框架无关的 PHP API,您将使用 PHP 创建 API 来补充您的 Nuxt 应用程序。您将学习如何安装 Apache 服务器和 PHP 引擎,了解 HTTP 消息和 PHP 标准,将 MySQL 安装为您的数据库系统,为 MySQL 编写 CRUD 操作,通过遵守 PHP 标准创建与框架无关的 PHP API,然后将您的 API 与 Nuxt 应用程序集成。
第十七章,使用 Nuxt 创建实时应用程序,您将使用 RethinkDB、Socket.IO 和 Koa 开发实时 Nuxt 应用程序。您将学习如何安装 RethinkDB,介绍 ReQL,将 RethinkDB 集成到您的 Koa API 中,将 Socket.IO 添加到 API 和 Nuxt 应用程序中,最终将您的 Nuxt 应用程序转换为具有 RethinkDB changefeeds 的实时 Web 应用程序。
第十八章,使用 CMS 和 GraphQL 创建 Nuxt 应用程序,您将使用(无头)CMS 和 GraphQL 来补充您的 Nuxt 应用程序。您将学习如何将 WordPress 转换为无头 CMS,在 WordPress 中创建自定义文章类型并扩展 WordPress REST API。您将学习如何在 Nuxt 应用程序中使用 GraphQL,了解 GraphQL 模式和解析器,使用 Appolo Server 创建 GraphQL API,并使用 Keystone.js GraphQL API。此外,您还将学习如何安装和保护 PostgreSQL 和 MongoDB,使用 Nuxt 生成静态站点,并从远程 API 流式传输资源(图像和视频),无论是 REST API 还是 GraphQL API。
本书最大的收获
在整本书中,您将需要一个 Nuxt.js 的版本-最新版本,如果可能的话。所有代码示例都是在 Ubuntu 20.10 上使用 Nuxt 2.14.x 进行测试的。以下是本书的其他必要软件、框架和技术列表:
书中涵盖的软件/硬件
操作系统要求
Koa.js v2.13.0
任何平台
Axios v0.19.2
任何平台
Keystone.js v11.2.0
任何平台
Socket.IO v2.3.0
任何平台
MongoDB v4.2.6
任何平台
MySQL v10.3.22-MariaDB
任何平台
RethinkDB v2.4.0
Linux, macOS
PHP v7.4.5
任何平台
Foundation v6.6.3
任何平台
Swiper.js v6.0.0
任何平台
Node.js v12.18.2 LTS (至少 v8.9.0)
任何平台
NPM v6.14.7
任何平台
如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将有助于避免与复制和粘贴代码相关的潜在错误。
下载示例代码文件
您可以从您在www.packt.com的账户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,文件将直接发送到您的邮箱。
您可以按照以下步骤下载代码文件:
登录或注册网址为www.packt.com。
选择“支持”选项卡。
点击“代码下载”。
在搜索框中输入书名,并按照屏幕上的说明进行操作。
下载文件后,请确保使用最新版本的解压缩软件解压缩文件夹:
Windows 的 WinRAR/7-Zip
Mac 的 Zipeg/iZip/UnRarX
Linux 的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-on-Nuxt.js-Web-Development。如果代码有更新,将在现有的 GitHub 存储库中进行更新。
我们还有来自丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。快去看看吧!
使用的约定
本书中使用了许多文本约定。
CodeInText:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:"然后,您可以在 .css 文件中创建过渡样式。"
代码块设置如下:
// pages/about.vue
export default {
transition: {
name: 'fade'
当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)
任何命令行输入或输出都以如下形式书写:
$ npm i less --save-dev
$ npm i less-loader --save-dev
粗体:表示一个新术语,一个重要词,或者屏幕上看到的词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“选择手动选择功能以从提示你选择的选项中选择路由器,以选择你需要的功能。”
警告或重要提示会显示为这样。提示和技巧会显示为这样。
第一部分:你的第一个 Nuxt 应用程序
在本节中,我们将简要介绍 Nuxt,其特点,文件夹结构等。然后,我们将通过一些简单的步骤开始构建我们的第一个 Nuxt 应用程序,并集成 Nuxt 路由,配置,Vue 组件等。
本节包括以下章节:
第一章,介绍 Nuxt
第二章,开始使用 Nuxt
第三章,添加 UI 框架
介绍 Nuxt
欢迎来到您的Nuxt.js Web 开发实践之旅。在本章中,我们将深入了解 Nuxt,看看构成这个框架的是什么。我们将带您了解 Nuxt 的特性,您将了解 Nuxt 所属的不同类型应用程序的优缺点。最后但同样重要的是,您将发现使用 Nuxt 作为通用 SSR 应用程序、静态站点生成器和单页面应用程序的巨大潜力。
在本章中,我们将涵盖以下主题:
从 Vue 到 Nuxt
为什么使用 Nuxt?
应用程序类型
Nuxt 作为通用 SSR 应用程序
Nuxt 作为静态站点生成器
Nuxt 作为单页面应用程序
让我们开始吧!
第一章:从 Vue 到 Nuxt
Nuxt 是一个更高级的 Node.js Web 开发框架,用于创建可以以两种不同模式开发和部署的 Vue 应用程序:通用(SSR)或单页面应用程序(SPA)。此外,您可以在 Nuxt 中部署 SSR 和 SPA 作为静态生成的应用程序。尽管您可以选择 SPA 模式,但 Nuxt 的全部潜力在于其通用模式或用于构建通用应用程序的服务器端渲染(SSR)。通用应用程序用于描述可以在客户端和服务器端都执行的 JavaScript 代码。但是,如果您希望开发经典(或标准/传统)的 SPA,仅在客户端执行的 SPA,您可能希望考虑使用纯 Vue。
请注意,SPA 模式的 Nuxt 应用程序与经典 SPA 略有不同。您将在本书的后面和本章中简要了解更多信息。
Nuxt 是建立在 Vue 之上的,具有一些额外功能,如异步数据、中间件、布局、模块和插件,可以先在服务器端执行您的应用程序,然后再在客户端执行。这意味着应用程序通常比传统的服务器端(或多页面)应用程序渲染更快。
Nuxt 预装了以下软件包,因此您无需像在标准 Vue 应用程序中那样安装它们:
Vue(vuejs.org/)
Vue 路由器(router.vuejs.org/)
Vuex(vuex.vuejs.org/)
Vue 服务器渲染器(ssr.vuejs.org/)
Vue 元(vue-meta.nuxtjs.org/)
除此之外,Nuxt 使用 webpack 和 Babel 来编译和捆绑您的代码,使用以下 webpack 加载器:
Vue 加载器(vue-loader.vuejs.org/)
Babel Loader (webpack.js.org/loaders/babel-loader/)
简而言之,webpack 是一个模块打包工具,它将 JavaScript 应用程序中的所有脚本、样式、资产和图像捆绑在一起,而 Babel 是一个 JavaScript 编译器,它将下一代 JavaScript(ES2015+)编译或转译为浏览器兼容的 JavaScript(ES5),以便您可以在当前浏览器上运行您的代码。
有关 webpack 和 Babel 的更多信息,请分别访问webpack.js.org/和babeljs.io/。
webpack 使用他们称之为加载器的东西来预处理您通过 JavaScript import语句或require方法导入的文件。您可以编写自己的加载器,但在编译 Vue 文件时,您无需这样做,因为它们已经由 Babel 社区和 Vue 团队为您创建。我们将在下一节中发现 Nuxt 带来的伟大功能以及这些加载器贡献的功能。
为什么使用 Nuxt?
由于传统 SPA 和多页面应用(MPA)的缺点,存在诸如 Nuxt 之类的框架。我们可以将 Nuxt 视为服务器端渲染 MPA 和传统 SPA 的混合体。因此,它被称为“通用”或“同构”。因此,能够进行服务器端渲染是 Nuxt 的定义特性。在本节中,我们将为您介绍 Nuxt 的其他突出特性,这将使您的应用开发变得简单而有趣。我们将首先介绍的功能允许您通过在文件中使用.vue扩展名来编写单文件 Vue 组件。
编写单文件组件
我们可以使用几种方法来创建 Vue 组件。全局 Vue 组件是通过使用Vue.component创建的,如下所示:
Vue.component('todo-item', {...})
另一方面,可以使用普通 JavaScript 对象创建本地 Vue 组件,如下所示:
const TodoItem = {...}
这两种方法在小型项目中使用 Vue 是可管理和可维护的,但是当你一次拥有大量具有不同模板、样式和 JavaScript 方法的组件时,对于大型项目来说,管理变得困难。
因此,单文件组件来拯救,我们只使用一个.vue文件来创建每个 Vue 组件。如果您的应用程序需要多个组件,只需将它们分开成多个.vue文件。在每个文件中,您可以只编写与该特定组件相关的模板、脚本和样式,如下所示:
// pages/index.vue
{{ message }}
export default {
data () {
return { message: 'Hello World' }
}
}
p {
font-size: 2em;
text-align: center;
}
在这里,您可以看到我们有一个 HTML 模板,它从 JavaScript 脚本中打印消息,并且描述模板的 CSS 样式,全部在一个.vue文件中。这使得您的代码更加结构化、可读和可组织。很棒,不是吗?这只能通过vue-loader和 webpack 实现。在 Nuxt 中,我们只在.vue文件中编写组件,无论它们是/components/、/pages/还是/layouts/目录中的组件。我们将在第二章中更详细地探讨这一点,开始使用 Nuxt。现在,我们将看一下 Nuxt 功能,它允许您直接编写 ES6 JavaScript。
编写 ES2015+
Nuxt 在不需要您担心配置和安装 Babel 在 webpack 的情况下,即可编译您的 ES6+代码。这意味着您可以立即编写 ES6+代码,并且您的代码将被编译为可以在旧版浏览器上运行的 JavaScript。例如,当使用asyncData方法时,您经常会看到以下解构赋值语法:
// pages/about.vue
export default {
async asyncData ({ params, error }) {
//...
}
}
在前面的代码中,使用解构赋值语法将 Nuxt 上下文中的属性解包到不同的变量中,以便我们可以在asyncData方法中使用这些变量进行逻辑处理。
有关 Nuxt 上下文和 ECMAScript 2015 功能的更多信息,请分别访问nuxtjs.org/api/context和babeljs.io/docs/en/learn/。
在 Nuxt 中编写 ES6 只能通过babel-loader和 webpack 实现。在 Nuxt 中,您可以编写更多内容,包括async函数、await运算符、箭头函数、import语句等。那么 CSS 预处理器呢?如果您使用 Sass、Less 或 Stylus 等流行的 CSS 预处理器编写 CSS 样式,但如果您是 Sass 用户而不是 Less 用户或 Stylus 用户,Nuxt 是否支持它们中的任何一个?简短的答案是是。我们将在下一节中找出这个问题的长答案。
使用预处理器编写 CSS
在 Nuxt 中,您可以选择喜欢的 CSS 预处理器来编写应用程序的样式,无论是 Sass、Less 还是 Stylus。它们已经在 Nuxt 中为您预配置。您可以在github.com/nuxt/nuxt.js/blob/dev/packages/webpack/src/config/base.js查看它们的配置。因此,您只需要在 Nuxt 项目中安装预处理器及其 webpack 加载程序。例如,如果您想将 Less 作为 CSS 预处理器,只需在 Nuxt 项目中安装以下依赖项:
$ npm i less --save-dev
$ npm i less-loader --save-dev
然后,您可以通过在
从这个例子中,您可以看到在 Nuxt 中编写现代 CSS 样式就像在 Nuxt 中编写现代 JavaScript 一样容易。您只需要安装您喜欢的 CSS 预处理器及其 webpack 加载程序。在本书的后续章节中,我们将使用 Less,但现在让我们找出 Nuxt 提供了哪些其他功能。
有关这些预处理器及其 webpack 加载程序的更多信息,请访问以下链接:
lesscss.org/ 用于 Less
webpack.js.org/loaders/less-loader/ 用于 less-loader
sass-lang.com/ 用于 Sass
webpack.js.org/loaders/sass-loader/ 用于 sass-loader
stylus-lang.com/ 用于 Stylus
github.com/shama/stylus-loader 用于 stylus-loader
尽管 PostCSS 不是预处理器,但如果您想在 Nuxt 项目中使用它,请访问提供的指南nuxtjs.org/faq/postcss-plugins。
使用模块和插件扩展 Nuxt
Nuxt 是建立在模块化架构之上的。这意味着您可以使用无数的模块和插件来扩展它,适用于您的应用程序或 Nuxt 社区。这也意味着您可以从 Nuxt 和 Vue 社区中选择大量的模块和插件,这样您就不必为您的应用程序重新发明它们。这些链接如下:
Nuxt.js 的精彩模块github.com/nuxt-community/awesome-nuxt#official
在github.com/vuejs/awesome-vue#components--libraries上查看令人敬畏的 Vue.js,用于 Vue 组件、库和插件
模块和插件只是 JavaScript 函数。现在不用担心它们之间的区别;我们将在第六章中讨论这个问题,编写插件和模块。
在路由之间添加过渡
与传统的 Vue 应用程序不同,在 Nuxt 中,您不必使用包装器
// pages/about.vue
export default {
transition: {
name: 'fade'
}
}
然后,你可以在.css文件中创建过渡样式:
// assets/transitions.css
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-leave,
.fade-enter-to {
opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 3s;
}
当导航到/about路由时,“fade”过渡将自动应用于about页面。很酷,不是吗?如果此时代码或类名看起来有点令人不知所措,不要担心;我们将在第四章中更详细地了解和探索这个过渡特性。
管理
元素此外,与传统的 Vue 应用程序不同,您可以直接管理应用程序的
块,而无需安装额外处理它的 Vue 包vue-meta。您只需通过head属性向任何页面添加所需的// nuxt.config.js
export default {
head: {
title: 'My Nuxt App',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'My Nuxt app is
about...' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
}
}
Nuxt 将为您将此数据转换为 HTML 标记。同样,我们将在第四章中更详细地了解和探索此功能,添加视图、路由和过渡。
使用 webpack 捆绑和拆分代码
Nuxt 使用 webpack 将您的代码捆绑、缩小并拆分为可以加快应用程序加载时间的块。例如,在一个简单的 Nuxt 应用程序中有两个页面,index/home 和 about,您将为客户端获得类似的块:
Hash: 0e9b10c17829e996ef30
Version: webpack 4.43.0
Time: 4913ms
Built at: 06/07/2020 21:02:26
Asset Size Chunks Chunk Names
../server/client.manifest.json 7.77 KiB [emitted]
LICENSES 389 bytes [emitted]
app.3d81a84.js 51.2 KiB 0 [emitted] [immutable] app
commons/app.9498a8c.js 155 KiB 1 [emitted] [immutable] commons/app
commons/pages/index.8dfce35.js 13.3 KiB 2 [emitted] [immutable] commons/pages/index
pages/about.c6ca234.js 357 bytes 3 [emitted] [immutable] pages/about
pages/index.f83939d.js 1.21 KiB 4 [emitted] [immutable] pages/index
runtime.3d677ca.js 2.38 KiB 5 [emitted] [immutable] runtime
+ 2 hidden assets
Entrypoint app = runtime.3d677ca.js commons/app.9498a8c.js app.3d81a84.js
您将为服务器端获取的块如下所示:
Hash: 8af8db87175486cd8e06
Version: webpack 4.43.0
Time: 525ms
Built at: 06/07/2020 21:02:27
Asset Size Chunks Chunk Names
pages/about.js 1.23 KiB 1 [emitted] pages/about
pages/index.js 6.06 KiB 2 [emitted] pages/index
server.js 80.9 KiB 0 [emitted] app
server.manifest.json 291 bytes [emitted]
+ 3 hidden assets
Entrypoint app = server.js server.js.map
这些块和构建信息是在使用 Nuxt npm run build 命令构建应用以进行部署时生成的。我们将在第十四章中更详细地了解这一点,使用 Linters、Formatters 和部署命令。
除此之外,Nuxt 还使用了 webpack 的其他出色功能和插件,比如静态文件和资源服务(资源管理),热模块替换,CSS 提取(extract-css-chunks-webpack-plugin),构建和监视时的进度条(webpackbar)等等。更多信息,请访问以下链接:
webpack.js.org/guides/code-splitting/ 用于代码拆分
webpack.js.org/concepts/manifest/ 用于清单
webpack.js.org/guides/asset-management/ 用于资源管理
webpack.js.org/concepts/hot-module-replacement/ 用于热模块替换
webpack.js.org/plugins/mini-css-extract-plugin/ 用于 CSS 提取
github.com/nuxt/webpackbar 用于 webpackbar(Nuxt 核心团队开发的插件)
来自 webpack、Babel 和 Nuxt 本身的这些出色功能将使您的现代项目开发变得有趣且轻松。现在,让我们看看各种应用类型,看看在构建下一个 web 应用时,为什么应该或不应该使用 Nuxt。
应用类型
今天的 web 应用与几十年前的应用非常不同。在那些日子里,我们的选择和解决方案更少。今天,它们正在蓬勃发展。无论我们称它们为“应用”还是“应用程序”,它们都是一样的。在本书中,我们将称它们为“应用”。因此,我们可以将我们当前的 web 应用分类如下:
传统的服务器端渲染应用
传统的单页应用
通用 SSR 应用
静态生成的应用
让我们逐个了解它们,并了解其利弊。我们首先来看最古老的应用类型 - 传统的服务器端渲染应用。
传统的服务器端渲染应用
服务器端呈现是向浏览器客户端传递数据和 HTML 的最常见方法。在网络行业刚刚开始时,这是唯一的做事方式。在传统的服务器呈现的应用程序或动态网站中,每个请求都需要从服务器重新呈现新页面到浏览器。这意味着您将在每次发送请求到服务器时重新加载所有脚本、样式和模板。重新加载和重新呈现的想法一点也不吸引人。尽管如今可以通过使用 AJAX 来减轻一些重新加载和重新呈现的负担,但这会给应用程序增加更多复杂性。
让我们来看看这些类型应用程序的优缺点。
优势:
更好的 SEO 性能:因为客户端(浏览器)得到了包含所有数据和 HTML 标记的完成页面,特别是属于页面的元标记,搜索引擎可以爬取页面并对其进行索引。
更快的初始加载时间:因为页面和内容是由服务器端脚本语言(如 PHP)在发送到客户端浏览器之前在服务器端呈现的,所以我们在客户端很快就能得到呈现的页面。此外,无需像传统的单页应用程序那样在 JavaScript 文件中编译网页和内容,因此应用程序在浏览器上加载更快。
缺点:
用户体验较差:因为每个页面都必须重新呈现,这个过程在服务器上需要时间,用户必须等待直到在浏览器上重新加载所有内容,这可能会影响用户体验。大多数情况下,我们只希望在提供新请求时获得新数据;我们不需要重新生成 HTML 基础,例如导航栏和页脚,但仍然会重新呈现这些基本元素。我们可以利用 AJAX 来仅呈现特定组件,但这会使开发变得更加困难和复杂。
后端和前端逻辑的紧密耦合:视图和数据通常在同一个应用程序中处理。例如,在典型的 PHP 框架应用程序中,如 Laravel,您可以在路由中使用模板引擎(如 Laravel Pug)渲染视图。或者,如果您正在为传统的服务器端渲染应用程序使用 Express,您可以使用模板引擎(如 Pug 或 vuexpress)来渲染视图。在这两个框架中,视图与后端逻辑耦合在一起,即使我们可以使用模板引擎提取视图层。后端开发人员必须知道每个特定路由或控制器要使用的视图(例如home.pug)。另一方面,前端开发人员必须在与后端开发人员相同的框架中处理视图。这给项目增加了更多复杂性。
传统的单页面应用程序(SPA)
与服务器端渲染应用程序相反,SPA 是客户端渲染(CSR)应用程序,它使用 JavaScript 在浏览器中渲染内容,而不需要在使用过程中重新加载新页面。因此,您不会将内容呈现到 HTML 文档中,而是从服务器获取基本的 HTML,然后在浏览器中使用 JavaScript 加载内容。
这是一个非常简单的 Vue 应用程序,其中您有一个容器
就是这样。您已经成功在您的 Nuxt 项目中安装并成功集成了它。现在,让我们在下一节中探讨如何使用 Foundation 创建网格结构布局和网站导航,以加速前端网页开发。
使用 Foundation 创建网格布局和网站导航
我们应该首先看一下 Foundation 的网格系统,它被称为 XY Grid。在网页开发中,网格系统是一种将我们的 HTML 元素结构化为基于网格的布局的系统。Foundation 带有我们可以轻松使用的 CSS 类来结构化我们的 HTML 元素,例如:
这将在大屏幕上(例如 iPad,Windows Surface)将我们的元素响应地结构化为两列,但在小屏幕上(例如 iPhone)将其结构化为单列。让我们在默认的index.vue页面和由create-nuxt-app脚手架工具生成的default.vue布局中创建一个响应式布局和网站导航:
删除/components/目录中的Logo.vue组件。
删除/pages/目录中index.vue页面中的
如果您想要将本地样式应用于特定页面或布局,这种方式是很好和可管理的。您应该在lang属性之前添加一个scoped属性,以便本地样式仅在特定页面上本地应用,并且不会干扰其他页面的样式。但是,如果您有多个页面和布局共享一个公共样式,那么您应该在项目的/assets/目录中全局创建样式。因此,让我们看看您如何在以下步骤中使用 Less 创建全局样式:
通过终端在 npm 上安装 Less 及其 webpack 加载器:
$ npm i less --save-dev
$ npm i less-loader --save-dev
在/assets/目录中创建一个main.less文件,并添加以下样式:
// assets/less/main.less @borderWidth: 1px;
@borderStyle: solid;
.cell {
border: @borderWidth @borderStyle blue;
}
.row {
border: @borderWidth @borderStyle red;
}
在 Nuxt 配置文件中安装上述全局样式如下:
// nuxt.config.js
export default {
css: [
'assets/less/main.less'
]
}
例如,在项目的任何地方应用上述样式:
// pages/index.vue


当你在浏览器上启动应用程序时,你应该看到刚刚添加到 CSS 类的边框。这些边框在开发布局时可以作为指南,因为网格系统下面的网格线是“不可见的”,没有可见的线可能很难将它们可视化。
你可以在我们的 GitHub 存储库的/chapter-3/nuxt-universal/adding-less/中找到上述代码。
由于我们在本节中涵盖了 CSS 预处理器,值得一提的是我们可以在
.blue {
color: blue;
}
有关 Sass 和 Scss 的更多信息,请访问sass-lang.com/。
在本书中,我们在各章节中主要使用 Less、原生 HTML 和 JavaScript(主要是 ECMAScript 6 或 ECMAScript 2015)。但你可以自由选择任何我们提到的预处理器。现在让我们来看看在 Nuxt 项目中为 HTML 元素添加效果和动画的另一种方法——jQuery UI。
添加 jQuery UI
jQuery UI 是建立在 jQuery 之上的一组用户界面(UI)交互、效果、小部件和实用工具。它对设计师和开发人员都是一个有用的工具。与 Motion UI 和 Foundation 一样,jQuery UI 可以帮助你用更少的代码在项目中做更多事情。它可以通过使用 CDN 资源和以 jQuery 为依赖项轻松地添加到普通 HTML 页面中,例如:
$('#accordion').accordion()
再次强调,与 Foundation 一样。当你想要将 jQuery UI 与 Nuxt 集成时会有一些复杂。我们可以使用上述 CDN 资源,并将它们添加到 Nuxt 配置文件中的head选项中,如下所示:
// nuxt.config.js
export default {
head: {
script: [
{ src: 'https://cdnjs.cloudflare.com/.../jquery.min.js' },
{ src: 'https://code.jquery.com/.../jquery-ui.js' },
],
link: [
{ rel: 'stylesheet', href:
'https://code.jquery.com/.../jquery-ui.css' },
]
}
}
但是,就像与 Foundation 集成一样,不鼓励这样做。以下是正确的做法:
在终端上通过 npm 安装 jQuery UI:
$ npm i jquery-ui-bundle
将 jQuery UI 的 CSS 源文件从/node_modules/文件夹添加到 Nuxt 配置文件的css选项中:
// nuxt.config.js
module.exports = {
css: [
'jquery-ui-bundle/jquery-ui.min.css'
]
}
在/plugins/目录中创建一个名为jquery-ui-bundle.js的文件,并按以下方式导入 jQuery UI:
// plugins/client-only/jquery-ui-bundle.client.js
import 'jquery-ui-bundle'
再次强调,此插件将确保 jQuery UI 仅在客户端上运行,并且我们将在第六章中更详细地介绍插件和模块的内容。
在 Nuxt 配置文件的plugins选项中注册前面的 jQuery UI 插件,如下所示:
// nuxt.config.js
export default {
plugins: [
'~/plugins/client-only/jquery-ui-bundle.client.js',
],
}
现在您可以在任何地方使用 jQuery UI,例如:
// pages/index.vue
import $ from 'jquery'
export default {
mounted () {
$('#accordion').accordion()
}
}
在此示例中,我们使用了 jQuery UI 的一个小部件 Accordion 来显示可折叠的内容面板。您可以在jqueryui.com/accordion/找到 HTML 代码的详细信息。
除了小部件,jQuery UI 还带有动画缓动效果等效果。让我们看看如何在以下步骤中使用缓动效果创建动画:
在/pages/目录中创建一个名为animate.vue的新页面,并在块中添加以下元素:
// pages/animate.vue
Hello jQuery UI
在块中使用 jQuery 的animate函数和 jQuery UI 的缓动效果创建动画,如下所示:
// pages/animate.vue
import $ from 'jquery'
export default {
mounted () {
var state = true
$('h1').on('click', function() {
if (state) {
$(this).animate({
color: 'red', fontSize: '10em'
}, 1000, 'easeInQuint', () => {
console.log('easing in done')
})
} else {
$(this).animate({
color: 'black', fontSize: '2em'
}, 1000, 'easeOutExpo', () => {
console.log('easing out done')
})
}
state = !state
})
}
}
在此代码中,当单击元素时,我们使用easeInQuint缓动效果,再次单击时使用easeOutExpo缓动效果。单击时,元素的字体大小从2em变化到10em,再次单击时从10em变化到2em。对于文本颜色也是一样,当单击元素时,它在red和black之间进行动画变化。
刷新您的浏览器,您应该会看到我们已经将动画和缓动效果应用到H1上。
有关更多缓动效果,请访问api.jqueryui.com/easings/,有关 jQuery 动画函数的更多信息,请访问api.jquery.com/animate/。
如果您想了解 jQuery UI 的其他效果、小部件和实用工具,请访问jqueryui.com/。
尽管您可以使用 CSS 使用 Motion UI 创建动画和过渡效果,但是 jQuery UI 是另一种选项,可以使用 JavaScript 对 HTML 元素应用动画。除了 jQuery 和 jQuery UI 之外,还有其他 JavaScript 库,我们可以从中受益,以特定方式交互地和有趣地呈现我们的内容,例如在向上或向下滚动页面时对内容进行动画处理,以及从左侧或右侧滑入内容。我们将在接下来的部分中了解这两个动画工具,即 AOS 和 Swiper。让我们在下一节中进行。
您可以在我们的 GitHub 存储库的/chapter-3/nuxt-universal/adding-jquery-ui/中找到本节中使用的所有代码。
添加 AOS
AOS 是一个 JavaScript 动画库,可以在您向下(或向上)滚动页面时将 DOM 元素美观地动画显示出来。这是一个小型库,非常容易使用,可以在滚动页面时触发动画,而无需自己编写代码。要对元素进行动画处理,只需使用data-aos属性:
就像这样简单,当您滚动页面时,元素将逐渐淡入。您甚至可以设置动画完成的持续时间。因此,让我们找出如何在以下步骤中将此库添加到您的 Nuxt 项目中:
在终端上通过 npm 安装 AOS:
$ npm i aos
将以下元素添加到index.vue中:
// pages/index.vue



在此模板中,我们使用 Foundation 为元素添加网格结构,并通过使用data-aos属性在每个元素上应用 AOS fade-up动画。
在
当您刷新屏幕时,您应该看到元素逐个向上淡入,按顺序出现,就像您向下滚动页面一样。这样可以让您如此轻松地美观地呈现您的内容,是不是很棒?
然而,我们刚刚应用 AOS 的方式并不适合如果您还有其他页面需要进行动画处理。您需要将前面的脚本复制到需要 AOS 动画的每个页面上。因此,如果您有多个页面需要使用 AOS 进行动画处理,那么您应该进行全局注册和初始化。让我们在以下步骤中找出如何做到这一点:
在/plugins/目录中创建一个aos.client.js插件,导入 AOS 资源,并初始化 AOS 如下:
// plugins/client-only/aos.client.js
import 'aos/dist/aos.css'
import aos from 'aos'
aos.init({
duration: 2000,
})
在这个插件中,我们指示 AOS 全局地花费 2 秒来动画化我们的元素。您可以在 https://github.com/michalsnik/aos#1-initialize-aos 找到其余的设置选项。
在 Nuxt 配置文件的plugins选项中注册前面的 AOS 插件如下:
// nuxt.config.js
module.exports = {
plugins: [
'~/plugins/client-only/aos.client.js',
],
}
就是这样。现在您可以将 AOS 动画应用于多个页面,而无需复制脚本。
请注意,我们在 AOS 插件中直接导入 CSS 资源,而不是通过 Nuxt 配置文件中的css选项全局导入,与我们在以前的部分中为 Foundation 和 Motion UI 所做的相反。因此,如果您想为 Foundation 做同样的事情,可以直接将其 CSS 资源导入到插件文件中,如下所示:
// plugins/client-only/foundation-site.client.js
import 'foundation-sites/dist/css/foundation.min.css'
import 'foundation-sites'
然后,您无需在 Nuxt 配置文件中使用全局的css选项。如果您希望保持配置文件“轻量”并将 UI 框架的 CSS 和 JavaScript 资源保留在其插件文件中,这种方式是首选。
您可以在我们的 GitHub 存储库的/chapter-3/nuxt-universal/adding-aos/中找到此示例 Nuxt 应用程序的源代码。
如果您想了解有关 AOS 和其余动画名称的更多信息,请访问 https://michalsnik.github.io/aos/。
现在让我们探索最后一个 JavaScript 助手,可以帮助加速您的前端开发 - Swiper。
添加 Swiper
Swiper 是一个 JavaScript 触摸滑块,可用于现代 Web 应用程序(桌面或移动)和移动本机或混合应用程序。它是 Framework7(https://framework7.io/)和 Ionic Framework(https://ionicframework.com/)的一部分,用于构建移动混合应用程序。我们可以像在以前的部分中使用 CDN 资源一样轻松地为 Web 应用程序设置 Swiper。但让我们看看您如何在以下步骤中以正确的方式在 Nuxt 中安装和使用它:
在您的 Nuxt 项目中通过终端使用 npm 安装 Swiper:
$ npm i swiper
添加以下 HTML 元素以在块中创建图像滑块:
// pages/index.vue
从这些元素中,我们希望创建一个图像滑块,其中包含三个图像,可以从左侧或右侧滑入视图,以及两个按钮 - 下一个按钮和上一个按钮。
在
在这个脚本中,我们向 Swiper 提供了我们图像滑块的类名,以便可以初始化一个新实例。此外,我们通过 Swiper 的pagination选项将下一个和上一个按钮注册到新实例。
您可以在swiperjs.com/api/找到用于初始化 Swiper 和与实例交互的 API 的其余设置选项。
在
在这个样式中,我们只想通过在 CSS 的width和height属性上使用 100%,并通过使用 CSS 的flex属性将图像置于滑块容器中央,使幻灯片占据整个屏幕。
现在,您可以运行 Nuxt 并在浏览器中加载页面,您应该会看到一个交互式图像滑块很好地工作。
您可以在 Swiper 官方网站swiperjs.com/demos/找到一些很棒的示例幻灯片。
请注意,我们刚刚使用的 Swiper 方式仅适用于单个页面。如果您想在多个页面上创建滑块,则可以通过插件全局注册 Swiper。因此,让我们在以下步骤中了解如何做到这一点:
在/plugins/目录中创建一个名为swiper.client.js的插件,导入 Swiper 资源,并创建一个名为$swiper的属性。将 Swiper 附加到此属性,并将其注入到 Vue 实例中,如下所示:
// plugins/client-only/swiper.client.js
import 'swiper/swiper-bundle.css'
import Vue from 'vue'
import Swiper from 'swiper/bundle'
Vue.prototype.$swiper = Swiper
在 Nuxt 配置文件的plugins选项中注册此 Swiper 插件:
// nuxt.config.js
export default {
plugins: [
'~/plugins/client-only/swiper.client.js',
],
}
现在,您可以通过使用this关键字调用$swiper属性,在应用的多个页面中创建 Swiper 的新实例,例如:
// pages/global.vue
export default {
mounted () {
var swiper = new this.$swiper('.swiper-container', { ... })
}
}
同样,我们将 CSS 资源组织在插件文件中,而不是通过 Nuxt 配置文件中的css选项全局注册它。但是,如果您想要全局覆盖这些 UI 框架和库中的一些样式,那么通过在css选项中全局注册它们的 CSS 资源,然后在/assets/目录中存储的 CSS 文件中添加自定义样式,更容易覆盖它们。
您可以从我们的 GitHub 存储库的/chapter-3/nuxt-universal/adding-swiper/中下载本章的源代码。如果您想了解更多关于 Swiper 的信息,请访问swiperjs.com/。
干得好!您已经成功掌握了我们为您选择的一些流行的 UI 框架和库,以加速您的前端开发。我们希望它们将对您未来创建的 Nuxt 项目有所帮助。在接下来的章节中,我们将偶尔使用这些框架和库,特别是在最后一章 - [第十八章]《使用 CMS 和 GraphQL 创建 Nuxt 应用》中。现在,让我们总结一下您在本章学到的内容。
总结
在本章中,您已经将 Foundation 安装为 Nuxt 项目中的主要 UI 框架,并使用 Foundation 的网格系统、JavaScript 实用程序和插件来创建简单的网格布局、表单和导航。您已经使用 Foundation 的 Motion UI 来创建 Sass 动画和过渡,还使用了 Foundation Icon Fonts 3 来向 HTML 页面添加常见和有用的图标。您已经安装了 Less 作为样式预处理器,并在 Less 样式表中创建了一些变量。
您已经安装了 jQuery UI,并将其手风琴小部件添加到您的应用程序中,并使用其缓动效果创建了动画。您已经安装了 AOS,并在向下或向上滚动页面时使用它来使元素动画进入视口。最后,您已经安装了 Swiper 来创建一个简单的图像幻灯片。最后但同样重要的是,您已经学会了如何通过 Nuxt 配置文件全局安装这些框架和库,或者仅在特定页面上局部使用它们。
在接下来的章节中,我们将介绍 Nuxt 中的视图、路由和过渡。您将创建自定义页面、路由和 CSS 过渡,并学习如何使用/assets/目录来提供图像和字体等资源。此外,您还将学习如何自定义默认布局并在/layouts/目录中添加新的布局。我们将提供一个简单的网站示例,该示例使用了所有这些 Nuxt 功能,以便您可以从本书中学到的内容中获得具体用途的感觉。因此,让我们在下一章中进一步探索 Nuxt!
第二部分:视图、路由、组件、插件和模块
在本节中,我们将开始添加路由、页面、模板、组件、插件、模块和 Vue 表单,使我们的 Nuxt 应用程序变得更加复杂和有趣。
本节包括以下章节:
第四章,添加视图、路由和过渡
第五章,添加 Vue 组件
第六章,编写插件和模块
第七章,添加 Vue 表单
添加视图、路由和过渡
在前一章中,您为前端 UI 框架和库创建了一些简单的页面、路由,甚至布局,但它们只是非常基本的。因此,在本章中,我们将深入研究每一个,以及 Nuxt 中的模板。您将自定义默认模板和布局,并创建自定义模板。您还将学习如何自定义全局 meta 标签,并将特定的 meta 标签添加到应用程序的各个页面。如果信息是有用的。您将为页面过渡创建 CSS 和 JavaScript 过渡和动画。因此,在本章结束时,您将能够通过本章学到的知识以及在上一章中学到的知识,交付一个简单但完全功能的 Web 应用程序或网站(带有一些虚拟数据)。
本章我们将涵盖的主题如下:
创建自定义路由
创建自定义视图
创建自定义过渡
第四章:创建自定义路由
如果我们要了解 Nuxt 中路由器的工作原理,我们首先应该了解它在 Vue 中的工作原理。然后我们可以理解如何在我们的 Nuxt 应用程序中实现它。传统 Vue 应用程序中的自定义路由是通过 Vue Router 创建的。因此,让我们首先了解一下 Vue Router 是什么。
介绍 Vue Router
Vue Router 是一个 Vue 插件,允许您在单页面应用程序(SPA)中创建强大的路由,而无需刷新页面即可在页面之间导航。一个快速的用法是,例如,如果我们想要一个用于所有用户但具有不同用户 ID 的User组件。您可以如下使用此组件:
const User = {
template: '
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
在这个例子中,任何以/user开头的路由后跟一个 ID(例如,/user/1或user/2)将被定向到User组件,该组件将使用该 ID 呈现模板。这仅在安装了 Vue 插件时才可能,因此让我们看看如何在下一节为 Vue 应用程序安装它,然后学习它在 Nuxt 应用程序中的工作原理。
有关 Vue Router 的更多信息,请访问router.vuejs.org/。
安装 Vue Router
在 Vue 中,您必须显式安装 Vue Router 以在传统的 Vue 应用程序中创建路由。即使您使用 Vue CLI(我们将在第十一章中介绍编写路由中间件和服务器中间件),您也必须选择手动选择功能以从提示您选择的选项中选择 Router,以选择您需要的功能。因此,让我们看看如何在本节中手动安装它。安装 Vue Router 有两种选项:
您可以使用 npm:
$ npm install vue-router
然后,在应用程序根目录中,通过Vue.use()显式导入vue-router:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
或者,您可以使用 CDN 或直接下载:
如果您使用 CDN,只需在 Vue 核心之后添加vue-router,其余安装将自行处理。安装 Vue Router 完成后,您可以使用它创建路由。
使用 Vue Router 创建路由
如果您使用 CDN 选项,首先在项目根目录中创建一个.html文件,其中包含以下基本 HTML 结构,并在
块中包含 CDN 链接://...
之后,您可以通过以下步骤快速启动 Vue Router:
在
块中使用以下标记创建应用程序基础:Hello App!
//...
在
请注意,在本章的即将到来的部分中,我们将介绍asyncData方法和第五章中的axios,所以现阶段不用担心它们。
创建一个单独的子组件,其中包含返回到子索引页面的链接:
// pages/users/_id.vue
{{ user.name }}
Users
import axios from 'axios'
export default {
async asyncData ({ params }) {
let { data } = await
axios.get('https://jsonplaceholder.typicode.com/users/'
+ params.id)
return { user: data }
}
}
你可以看到 Nuxt 已经帮你省去了在 Vue 应用中配置嵌套路由的步骤,使用了children属性(如前面 Vue 应用示例中的步骤 3所示)。
因此,在这个 Nuxt 应用中,当子页面渲染后,users.vue中的
Users
元素将始终可见。包含列表元素的- 元素将始终被子页面替换。如果父级信息在子页面中是持久的,这将非常有用,因为在子页面渲染时不需要重新请求父级信息。
-
{{
item.text }}
{{ item.price }}
-
-
- {{ user.name }} '
- {{ user.name }} '
-
{{ user.name }}
-
{{ user.name }}
你可以在我们的 GitHub 仓库的/chapter-4/nuxt-universal/routes/nested-routes/中找到这个示例应用。
由于存在用于“升级”基本路由的动态路由,您可能会问,嵌套路由的动态路由呢?从技术上讲,是的,这是可能的,它们被称为动态嵌套路由。因此,让我们在下一节中了解更多关于它们的信息。
创建动态嵌套路由
我们已经看到了动态路由和嵌套路由的工作原理,因此从理论上和技术上讲,可以将这两个选项结合起来,通过在动态父级(例如_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
您可以在我们的 GitHub 存储库的/chapter-4/nuxt-universal/routing/dynamic-nested-routes/中找到此类型路由的示例应用程序。
创建动态路由和页面始终需要路由中的参数(换句话说,路由参数),以便我们可以将它们(无论是 ID 还是 slug)传递给动态页面进行处理。但在处理和响应参数之前,验证它们是一个好主意。因此,让我们看看如何在下一个主题中验证路由参数。
验证路由参数
您可以在组件中使用validate方法来验证动态路由的参数,在进一步处理或异步获取数据之前验证参数。这种验证应该始终返回true以继续前进;如果得到false布尔值,Nuxt 将停止路由并立即抛出 404 错误页面。例如,您希望确保 ID 必须是一个数字:
// pages/users/_id.vue
export default {
validate ({ params }) {
return /^\d+$/.test(params.id)
}
}
因此,如果您使用localhost:3000/users/xyz请求页面,您将收到一个带有“此页面找不到”消息的 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
}
}
你还可以在validate方法中使用async进行await操作:
async validate({ params }) {
// ...
}
你还可以在validate方法中使用return承诺:
validate({ params }) {
return new Promise(...)
}
你可以在/chapter-4/nuxt-universal/routing/validate-route-params/中找到前面示例应用程序的 ID 验证。
在我们的 GitHub 存储库中。
验证路由参数是处理无效或未知路由的一种方式,但另一种处理它们的方式是使用_.vue文件来捕捉它们。所以,让我们在下一节中找出如何做。
使用 _.vue 文件处理未知路由
除了使用validate方法抛出通用404 页面外,你还可以使用_.vue文件抛出自定义错误页面。让我们通过以下步骤来探讨这是如何工作的:
在/pages/目录中创建一个空的_.vue文件,如下所示:
pages/
--| _.vue
--| index.vue
--| users.vue
--| users/
-----| _id.vue
-----| index.vue
为这个_.vue文件添加任何自定义内容,如下所示:
// pages/_.vue
Not found
Sorry, the page you are looking for is not found.
启动应用程序并导航到以下路由,你会发现 Nuxt 将调用这个_.vue文件来处理这些请求,这些请求不匹配正确的路由级别:
/company
/company/careers
/company/careers/london
/users/category/subject
/users/category/subject/type
如果你想在特定级别上抛出一个更具体的 404 页面 - 例如,仅在/users路由中 - 那么在/users/文件夹中创建另一个_.vue文件,如下所示:
pages/
--| _.vue
--| index.vue
--| users.vue
--| users/
-----| _.vue
-----| _id.vue
-----| index.vue
为这个_.vue文件添加自定义内容,如下所示:
// pages/users/_.vue
User Not found
Sorry, the user you are looking for is not found.
再次导航到以下路由,你会发现 Nuxt 不再调用这个/pages/_.vue文件来处理不匹配的请求:
/users/category/subject
/users/category/subject/type
相反,Nuxt 现在调用/pages/users/_.vue文件来处理它们。
你可以在我们的 GitHub 存储库的/chapter-4/nuxt-universal/routing/unknown-routes/中找到这个示例应用程序。
我们希望到现在为止,你应该知道如何以各种方式创建适合你的应用程序的路由,但是在 Nuxt 中,路由和页面是密不可分的,不可分割的,所以你还需要知道如何创建 Nuxt 页面,这些页面是自定义视图。你将在下一个主题中学习如何做到这一点。
创建自定义视图
你在上面创建的自定义路由中的每个路由都将落在一个“页面”上,该页面具有我们希望在前端显示的所有 HTML 标记和内容。从软件架构的角度来看,这些 HTML 标记和内容,包括元信息、图像和字体,都是你的应用程序的视图或呈现层。在 Nuxt 中,我们可以轻松地创建和自定义我们的视图。让我们来了解一下 Nuxt 视图的组成部分以及如何自定义它。
了解 Nuxt 视图
Nuxt 中的视图结构包括应用程序模板、HTML 头部、布局和页面层。你可以使用它们来为应用程序路由创建视图。在一个更复杂的应用程序中,你可以使用来自 API 的数据填充它们,而在一个简单的应用程序中,你可以直接手动嵌入虚拟数据。我们将在接下来的章节中逐一介绍这些层。在深入了解之前,请花一点时间研究下面的图表,它将为你提供 Nuxt 视图的完整视图:
参考来源:nuxtjs.org/guide/views
你可以看到文档-HTML 文件是 Nuxt 视图的最外层,其后是布局、页面和可选的页面子层和 Vue 组件层。文档-HTML 文件是你的 Nuxt 应用程序的应用程序模板。让我们首先从这个最基本的层开始,学习如何在下一节中自定义它。
自定义应用程序模板
Nuxt 在幕后为你创建 HTML 应用程序模板,因此基本上你不必费心去创建它。然而,你仍然可以自定义它,比如添加脚本或样式,如果你想的话。默认的 Nuxt HTML 模板就是这么简单:
{{ HEAD }}
{{ APP }}
如果你想要更改或覆盖这个默认值,只需在根目录中创建一个app.html文件。看下面的例子:
// app.html
{{ HEAD }}
{{ APP }}
重新启动你的应用程序,你会看到你的自定义应用程序 HTML 模板已经替换了 Nuxt 的默认模板。
你可以在我们的 GitHub 存储库的/chapter-4/nuxt-universal/view/app-template/中找到这个例子。
接下来最接近 HTML 文档(即元素)的一层是 HTML 头部,即
元素,其中包含重要的元信息以及页面的脚本和样式。我们不会直接在应用程序模板中添加或自定义这些数据,而是在 Nuxt 配置文件和/pages/目录中的文件中进行。因此,让我们在下一节中了解如何做到这一点。创建自定义 HTML 头部
一个 HTML 的
元素由请注意,自定义加载组件中必须暴露start和finish方法,以便 Nuxt 在路由更改时调用您的组件并使用这些方法(调用start方法)并加载(调用finish方法)。
因此,在这个组件中,加载元素始终是隐藏的,因为data方法中默认将loading属性设置为false。只有在路由更改时loading属性设置为true时,加载元素才会变为可见。然后在路由完成加载后,loading属性再次设置为false时,它会再次隐藏。
有关这些和其他可用方法的更多信息,请访问nuxtjs.org/api/configuration-loading。
在 Nuxt 配置文件的loading属性中包含前面自定义组件的路径:
// nuxt.config.js
export default {
loading: '~/components/loading.vue'
}
您可以在我们的 GitHub 存储库中的/chapter-4/nuxt-universal/view/custom-pages/loading-global-custom/中找到此示例。
我们还可以根据特定页面配置加载行为,如下所示:
// pages/about.vue
export default {
loading: false
}
如果页面上loading键的值为false,它将自动停止调用this.$nuxt.$loading.finish()和this.$nuxt.$loading.start()方法,这允许您在脚本中手动控制它们,如下所示:
// pages/about.vue
click here
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
Final Page
Go to /
在这个例子中,您可以看到您可以使用this.$nuxt.$loading.finish()和this.$nuxt.$loading.start()手动控制加载栏。加载栏在mounted方法中需要 5 秒钟才能完成。当您触发goToFinal方法时,加载栏立即开始,并且需要 5 秒钟才能将路由更改为/final。
您可以在我们的 GitHub 存储库中的/chapter-4/nuxt-universal/view/custom-pages/loading-page/中找到此示例。
过渡属性
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 }) {
//...
}
我们将在第十一章中深入讨论中间件,编写路由中间件和服务器中间件。
所以,就是这样。您已经完成了关于 Nuxt 视图的部分,从应用程序模板、HTML 头部和布局到页面。干得好!我们将在下一章中讨论 Vue 组件。但现在,我们应该看的下一件事是在 Nuxt 中创建自定义页面之间的过渡,因为过渡和页面是密切相关的,就像您之前简要介绍过的页面transition属性一样。所以,让我们继续进行本章的最后一个主题,您将在其中了解如何创建自定义过渡。
创建自定义过渡
到目前为止,您已经成功为 Nuxt 应用程序创建了多个路由和页面,并在页面之间切换时添加了一个加载栏。这已经使得应用程序看起来相当不错。但这并不是您在 Nuxt 中可以做的全部。您可以在页面之间添加更多令人惊叹的效果和过渡。这就是页面中的transition属性(例如,/pages/about.vue)以及 Nuxt 配置文件中的pageTransition和layoutTransition选项的用处。
我们可以通过 Nuxt 配置文件全局应用过渡,或者特定应用于某些页面。我们将指导您了解这个主题。但是,要理解 Nuxt 中过渡的工作原理,我们首先应该了解它在 Vue 中是如何工作的,然后我们可以学习如何在路由更改时在我们的页面上实现它。让我们开始吧。
理解 Vue 过渡
Vue 依赖于 CSS 过渡,并使用
hello world
你可以看到这有多么容易 - 你可以像吃蛋糕一样轻松地用
.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 过渡类简单地可视化为以下图表:
参考来源:vuejs.org/v2/guide/transitions.html
Vue 默认使用v-作为过渡类的前缀,但如果你想更改这个前缀,只需在
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-leave,
.fade-enter-to {
opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms;
}
让我们将前面的过渡应用到一个简单的 Vue 应用程序中,步骤如下:
创建两个简单的路由,并用
添加一个带有前缀fade-的 CSS 过渡类的
当你在浏览器上运行应用程序时,你会发现在切换路由时,路由组件淡入淡出需要 300 毫秒。
你可以在我们的 GitHub 存储库的/chapter-4/vue/transitions/basic.html中找到这个例子。
你可以看到,过渡需要一些 CSS 类来使其工作,但对于 Vue 应用程序来说,掌握它们并不难。现在,让我们看看如何在下一节中在 Nuxt 中应用过渡。
使用 pageTransition 属性进行过渡
在 Nuxt 中,不再需要
在
v-for="(todo, index) in todos" v-bind:key="index" v-bind:title="todo.title" >
在
在这个例子中,如果你在输入水果的名字并切换类型,你仍然会在vegetables输入框中看到你刚刚输入的名字。这是因为 Vue 试图尽可能地重用相同的元素以获得最快的结果。但这并不总是理想的。你可以通过为每个元素添加key属性以及一个唯一值来告诉 Vue 不要重用相同的元素,如下所示:
因此,如果您刷新页面并再次测试,输入字段现在应该按预期工作,而不会在切换它们时“重用”彼此。这不适用于
您可以在本书的 GitHub 存储库的/chapter-5/vue/component/key/目录中的toggle-with-key.html和toggle-without-key.html文件中找到此示例代码。
这就是您需要了解的关于 Vue 组件基本性质的全部内容。因此,到目前为止,您应该已经掌握了足够的基本知识,可以开始使用单文件组件创建 Vue 组件的下一个级别。让我们开始吧!
如果您想了解更多关于 Vue 组件以及 Vue 组件更深入的部分,例如插槽,请访问vuejs.org/v2/guide/components.html。
创建单文件 Vue 组件
我们一直在使用单个 HTML 页面编写 Vue 应用程序,以便快速获得我们想要看到的结果。但是在 Vue 或 Nuxt 的实际开发项目中,我们不希望编写这样的东西:
const Foo = { template: '
const Bar = { template: '
在前面的代码中,我们在一个地方(例如在单个 HTML 页面中)使用 JavaScript 对象创建了两个 Vue 组件,但最好将它们分开,并在单独的.js文件中创建每个组件,如下所示:
// components/foo.js
Vue.component('page-foo', {
data: function () {
return { message: 'foo' }
},
template: '
})
这对于简单的组件可以很好地工作,其中 HTML 布局很简单。但是,在涉及更复杂的 HTML 标记的更复杂布局中,我们希望避免在 JavaScript 文件中编写 HTML。这个问题可以通过具有.vue扩展名的单文件组件来解决,如下所示:
// index.vue
{{ message }}
export default {
data () {
return { message: 'Hello World!' }
}
}
p {
font-size: 2em;
text-align: center;
}
然而,我们不能只是在浏览器上运行该文件,而不使用构建工具(如 webpack 或 rollup)进行编译。在本书中,我们使用 webpack。这意味着,从现在开始,我们将不再使用 CDN 或单个 HTML 页面来创建复杂的 Vue 应用程序。相反,我们将使用.vue和.js文件,只有一个.html文件来创建我们的 Vue 应用程序。我们将在接下来的部分指导您如何使用 webpack 来帮助我们做到这一点。让我们开始吧。
使用 webpack 编译单文件组件
要编译.vue组件,我们需要将vue-loader和vue-template-compiler安装到 webpack 构建过程中。但在此之前,我们必须在项目目录中创建一个package.json文件,列出我们项目依赖的 Node.js 包。您可以在docs.npmjs.com/creating-a-package-json-file上查看package.json字段的详细信息。最基本和必需的是name和version字段。让我们开始吧:
在项目目录中创建一个package.json文件,其中包含以下必填字段和值:
// package.json
{
"name": "vue-single-file-component",
"version": "1.0.0"
}
打开一个终端,将目录更改为您的项目,并安装vue-loader和vue-template-compiler:
$ npm i vue-loader --save-dev
$ npm i vue-template-compiler --save-dev
您应该在终端上看到一个警告,因为您在此处安装的 Node.js 包需要其他 Node.js 包,其中最显着的是 webpack 包。在本书中,我们在本书的 GitHub 存储库中的/chapter-5/vue/component-webpack/basic/中设置了一个基本的 webpack 构建过程。我们将在大多数即将推出的 Vue 应用程序中使用此设置。我们已将 webpack 配置文件分成了三个较小的配置文件:
webpack.common.js包含在开发和生产过程中共享的常见 webpack 插件和配置。
webpack.dev.js仅包含开发过程的插件和配置。
webpack.prod.js仅包含生产过程的插件和配置。
以下代码显示了我们如何在script命令中使用这些文件:
// package.json
"scripts": {
"start": "webpack-dev-server --open --config webpack.dev.js",
"watch": "webpack --watch",
"build": "webpack --config webpack.prod.js"
}
请注意,在本书中,我们假设您已经知道如何使用 webpack 来编译 JavaScript 模块。如果您对 webpack 还不熟悉,请访问webpack.js.org/获取更多信息。
因此,在安装了vue-loader和vue-template-compiler之后,我们需要在webpack.common.js(或webpack.config.js,如果您使用单个配置文件)中配置module.rules,如下所示:
// webpack.common.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
]
}
然后,我们可以使用在package.json中设置的以下命令来查看我们的应用程序运行情况:
$ npm run start用于在localhost:8080进行实时重新加载和开发
$ npm run watch用于在/path/to/your/project/dist/进行开发
$ npm run build用于在/path/to/your/project/dist/编译我们的代码
就是这样。现在你有了一个基本的构建流程来开发 Vue 应用程序与 webpack。所以,从现在开始,在更复杂的应用程序中,我们将编写单文件组件,并使用这种方法来编译它们。我们将在下一节中创建一个简单的 Vue 应用程序。
在单文件组件中传递数据和监听事件
到目前为止,我们一直在使用单个 HTML 页面进行我们的“todo”演示。这一次,我们将使用单文件组件来创建一个简单的“todo”购物清单。让我们开始:
在
// index.html
Components)
在项目根目录中创建一个/src/目录,并在其中创建一个entry.js文件作为文件入口点,以指示 webpack 应该使用哪些模块来开始构建我们的应用程序内部依赖图。webpack 还将使用此文件来找出入口点依赖的其他模块和库(直接和间接)。
// src/entry.js
'use strict'
import Vue from 'vue/dist/vue.js'
import App from './app.vue'
new Vue({
el: 'todos',
template: '
components: {
App
}
})
在
在上面的代码中,我们简单地将子组件作为TodoItem导入,并使用v-for从groceryList中生成它们的列表。
在methods对象中添加以下方法以添加和删除项目。然后,在computed对象中添加一个方法,计算购物车中项目的总成本:
// src/app.vue
methods: {
addItem (item) {
this.cart.push(item)
this.total = this.shoppingCartTotal
},
deleteItem (item) {
this.cart.splice(this.cart.findIndex(e => e === item), 1)
this.total = this.shoppingCartTotal
}
},
computed: {
shoppingCartTotal () {
let prices = this.cart.map(item => item.price)
let sum = prices.reduce((accumulator, currentValue) =>
accumulator + currentValue, 0)
return sum
}
}
创建一个子组件,通过props显示从父级传递下来的项目:
// src/todo-item.vue
export default {
props: ['item'],
data () {
return { checked: false }
},
methods: {
addToCart (item) {
this.$emit('add-item', item)
}
},
watch: {
checked (boolean) {
if (boolean === false) {
return this.$emit('delete-item', this.item)
}
this.$emit('add-item', this.item)
}
}
}
在这个组件中,我们还有一个checkbox按钮。这用于发出delete-item或add-item事件,并将项目数据传递给父级。现在,如果你用$ npm run start运行应用程序,你应该看到它在localhost:8080加载。
干得好!你已经成功构建了一个使用 webpack 的 Vue 应用程序,这就是 Nuxt 在幕后使用的编译和构建你的 Nuxt 应用程序。了解已建立系统下方运行的内容总是有用的。当你知道如何使用 webpack 时,你可以使用刚学到的 webpack 构建设置来进行各种 JavaScript 和 CSS 相关的项目。
你可以在本书的 GitHub 存储库中的/chapter-5/vue/component-webpack/todo/中找到这个示例。
在下一节中,我们将把前面几节学到的内容应用到/chapter-5/nuxt-universal/local-components/sample-website/中的sample website示例网站中,这个示例可以在本书的 GitHub 存储库中找到。
在 Nuxt 中添加 Vue 组件
在示例网站中,我们只有两个.vue文件可以使用 Vue 组件进行改进:/layouts/default.vue和/pages/work/index.vue。首先,我们应该改进/layouts/default.vue。在这个文件中,我们只需要改进三件事:导航、社交媒体链接和版权。
重构导航和社交链接
我们将从重构导航和社交媒体链接开始:
在/components/目录中创建一个导航组件,如下所示:
// components/nav.vue
export default {
props: ['item']
}
在/components/目录中也创建一个社交链接组件,如下所示:
// components/social.vue
export default {
props: ['item']
}
将它们导入到布局的
在这里,您可以看到我们在 Nuxt 应用程序中使用的组件代码与我们在 Vue 应用程序中编写的代码相同。这些类型的组件是嵌套组件。props属性和$emit方法用于在父组件和子组件之间传递数据。这些嵌套组件也是本地的,因为它们只在导入它们的组件(父级)的范围内可用。因此,从另一个角度来看,Vue 组件可以被归类为本地组件和全局组件。自从什么是组件?部分以来,您一直在学习全局组件。但是,您只学会了如何在 Vue 应用程序中使用它们。在接下来的部分中,我们将看看如何为 Nuxt 应用程序注册全局组件。但在跳入其中之前,让我们从整体的角度重新审视 Vue 组件:全局组件和本地组件。
注册全局和本地组件
我们已经创建了许多组件,无论是使用Vue.component()、纯 JavaScript 对象还是单文件组件引擎。我们创建的一些组件是全局组件,而另一些是本地组件。例如,在上一节中刚刚创建的/components/目录中的所有重构组件都是本地组件,而在什么是组件?部分中创建的组件都是全局组件。无论它们是本地组件还是全局组件,如果您想使用它们,都必须进行注册。其中一些在创建时注册,而另一些则需要手动注册。在接下来的部分中,您将学习如何全局和本地注册它们。您还将了解两种类型的注册将如何影响您的应用程序。我们将学习如何注册 Vue 组件,而不是传递它们。
在 Vue 中注册全局组件
全局组件,正如它们的名称所示,可以在整个应用程序中全局使用。当您使用Vue.component()创建它们时,它们会被全局注册:
Vue.component('my-component-name', { ... })
全局组件必须在根 Vue 实例实例化之前注册。注册后,它们可以在根 Vue 实例的模板中使用,如下所示:
Vue.component('component-x', { ... })
Vue.component('component-y', { ... })
Vue.component('component-z', { ... })
new Vue({ el: '#app' })
在这里,您可以看到注册全局组件非常容易 - 在创建它们时,您甚至可能意识不到注册过程。我们将很快在Nuxt 中注册全局组件部分中研究这种类型的注册。但现在,我们将学习如何注册本地组件。
在 Vue/Nuxt 中注册本地组件
在本章中,我们已经看到并使用了 Vue 和 Nuxt 应用中的本地组件。这些组件是通过使用纯 JavaScript 对象创建的,如下所示:
var ComponentX = { ... }
var ComponentY = { ... }
var ComponentZ = { ... }
然后,它们可以通过components选项进行注册,如下所示:
new Vue({
el: '#app',
components: {
'component-x': ComponentX,
'component-y': ComponentY,
'component-z': ComponentZ
}
})
还记得我们在本书的 GitHub 存储库中的/chapter-5/vue/component/basic.html文件中创建的 Vue 应用吗?该应用中的user-item组件是一个全局组件。现在,让我们对其进行重构并将其变成一个本地组件:
移除以下全局组件:
Vue.component('user-item', {
props: ['user'],
template: '
})
使用以下方式替换为本地组件:
const UserItem = {
props: ['user'],
template: '
}
使用components选项注册本地组件:
new Vue({
el: '#app',
data: {
users: [
{ id: 0, name: 'John Doe' },
//...
]
},
components: {
'user-item': UserItem
}
})
该应用程序将与以前的方式相同工作。唯一的区别是user-item不再全局可用。这意味着它在任何其他子组件中都不可用。例如,如果您想要在ComponentZ中使ComponentX可用,那么您必须手动"附加"它:
var ComponentX = { ... }
var ComponentZ = {
components: {
'component-x': ComponentX
}
}
如果您正在使用 babel 和 webpack 编写 ES2015 模块,您可以将ComponentX作为单文件组件,然后导入它,如下所示:
// components/ComponentZ.vue
import Componentx from './Componentx.vue'
export default {
components: {
'component-x': ComponentX
}
}
v-for="item in items" ... >
您还可以从components选项中省略component-x,并直接在其中使用ComponentX变量,如下所示:
// components/ComponentZ.vue
export default {
components: {
ComponentX
}
}
在 ES2015+中使用诸如ComponentX之类的变量作为 JavaScript 对象的简写形式为ComponentX: ComponentX。由于component-x从未注册过,所以您需要在模板中使用
v-for="item in items" ... >
在前面的单文件组件中编写 ES2015 与我们在 Nuxt 中编写.vue文件的方式相同。因此,到目前为止,您应该已经意识到我们一直在 Nuxt 应用程序中编写本地组件,例如/components/copyright.vue和/components/nav.vue。但是在 Nuxt 应用程序中如何编写全局组件呢?这就是/plugins/目录发挥作用的地方。在下一节中,您将学习如何在 Nuxt 中进行此操作。
您可以在本书的 GitHub 存储库中的/chapter-5/vue/component/registering-local-components.html中找到前面的应用程序。
在 Nuxt 中注册全局组件
我们在第二章中学习了目录结构,开始使用 Nuxt,/plugins/目录是我们可以创建 JavaScript 文件并在实例化根 Vue 应用程序之前运行的最佳位置。因此,这是注册我们的全局组件的最佳位置。
让我们创建我们的第一个全局组件:
在/plugins/目录中创建一个简单的 Vue 组件,如下所示:
// components/global/sample-1.vue
{{ message }}
export default {
data () {
return {
message: 'A message from sample global component 1.'
}
}
}
在/plugins/目录中创建一个.js文件,并导入前面的组件,如下所示:
// plugins/global-components.js
import Vue from 'vue'
import Sample from '~/components/global/sample-1.vue'
Vue.component('sample-1', Sample)
我们还可以直接在/plugins/global-components.js中创建第二个全局组件,如下所示:
Vue.component('sample-2', {
render (createElement) {
return createElement('p', 'A message from sample global
component 2.')
}
})
告诉 Nuxt 在 Nuxt 配置文件中在实例化根应用程序之前先运行它们,如下所示:
// nuxt.config.js
plugins: [
'~/plugins/global-components.js',
]
请注意,此组件将在 Nuxt 应用程序的客户端和服务器端都可用。如果您只想在特定端上运行此组件,例如仅在客户端上运行,则可以注册它,如下所示:
// nuxt.config.js
plugins: [
{ src: '~/plugins/global-components.js', mode: 'client' }
]
现在,这个组件只能在客户端使用。但是,如果你只想在服务器端运行它,只需在前面的mode选项中使用server。
我们可以在任何地方使用这些全局组件,而无需手动再次导入它们,如下面的代码所示:
// pages/about.vue
在浏览器上运行应用程序。你应该得到以下输出:
A message from sample global component 1.
A message from sample global component 2.
就是这样!这就是你可以通过涉及各种文件在 Nuxt 中注册全局组件的方法。全局注册的底线是使用Vue.component,就像我们在 Vue 应用程序中所做的那样。然而,全局注册通常不是理想的,就像它的“表兄弟”全局混入一样,我们将在下一节中介绍。例如,全局注册组件但在大多数情况下不需要它们对于服务器和客户端来说都是不必要的。现在,让我们继续看看混入是什么,以及如何编写它们。
你可以在本书的 GitHub 存储库中的/chapter-5/nuxt-universal/global-components/中找到这个例子。
编写基本和全局混入
混入只是一个 JavaScript 对象,可以用来包含任何组件选项,比如created,methods,mounted等等。它们可以用来使这些选项可重用。我们可以通过将它们导入到组件中,并将它们与该组件中的其他选项“混合”来实现这一点。
在某些情况下,使用混入可能是有用的,比如在第二章中,开始使用 Nuxt。我们知道,当 Vue Loader 编译单文件组件中的块时,它会将遇到的任何资源 URL 转换为 webpack 模块请求,如下所示:

前面的图像将被转换为以下 JavaScript 代码:
createElement('img', {
attrs: {
src: require('~/assets/sample-1.jpg') // this is now a module request
}
})
如果您手动插入图像,这并不难。但在大多数情况下,我们希望动态插入图像,如下所示:
// pages/about.vue
const post = {
title: 'About',
image: {
src: '/about.jpg',
alt: 'Sample alt 1'
}
}
export default {
data () {
return { post }
}
}
在这个例子中,当您在控制台上使用:src指令时,图像将会得到 404 错误,因为 Vue Loader 在构建过程中从未编译它。为了解决这个问题,我们需要手动将模块请求插入到:src指令中。
然而,这也不好,因为更倾向于动态图像解决方案。因此,这里的解决方案如下:
在这个解决方案中,我们编写了一个可重用的loadAssetImage函数,以便在任何需要的 Vue 组件中调用它。因此,在这种情况下,我们需要混合。有几种使用混合的方法。我们将在接下来的几节中看一些常见的用法。
创建基本混合/非全局混合
在非单文件组件 Vue 应用程序中,我们可以这样定义一个混合对象:
var myMixin = {
created () {
this.hello()
},
methods: {
hello () { console.log('hello from mixin!') }
}
}
然后,我们可以使用Vue.extend()将其“附加”到一个组件中:
const Foo = Vue.extend({
mixins: [myMixin],
template: '
})
在这个例子中,我们只将这个混合附加到Foo,所以当调用这个组件时,你只会看到console.log消息。
你可以在这本书的 GitHub 存储库的/chapter-5/vue/mixins/basic.html中找到这个例子。
对于 Nuxt 应用程序,我们在/plugins/目录中创建并保存混合对象,保存在.js文件中。让我们来演示一下:
在/plugins/目录中创建一个mixin-basic.js文件,其中包含一个在浏览器控制台上打印消息的函数,当 Vue 实例被创建时:
// plugins/mixin-basic.js
export default {
created () {
this.hello()
},
methods: {
hello () {
console.log('hello from mixin!')
}
}
}
在需要的地方随时导入它,如下所示:
// pages/about.vue
import Mixin from '~/plugins/mixin-basic.js'
export default {
mixins: [Mixin]
}
在这个例子中,只有当你在/about路由上时,你才会得到console.log消息。这就是我们创建和使用非全局混合的方法。但在某些情况下,我们需要全局混合适用于应用程序中的所有组件。让我们看看我们如何做到这一点。
你可以在这本书的 GitHub 存储库的/chapter-5/nuxt-universal/mixins/basic/中找到这个例子。
创建全局混合
我们可以通过使用Vue.mixin()来创建和应用全局混合:
Vue.mixin({
mounted () {
console.log('hello from mixin!')
}
})
全局混合必须在实例化 Vue 实例之前定义:
const app = new Vue({
//...
}).$mount('#app')
现在,你创建的每个组件都将受到影响并显示该消息。你可以在这本书的 GitHub 存储库的/chapter-5/vue/mixins/global.html中找到这个例子。如果你在浏览器上运行它,你会看到console.log消息出现在每个路由上,因为它在所有路由组件中传播。通过这种方式,我们可以看到如果被滥用可能造成的潜在危害。在 Nuxt 中,我们以相同的方式创建全局混合;也就是使用Vue.mixin()。让我们来看一下:
在/plugins/目录中创建一个mixin-utils.js文件,以及用于从/assets/目录加载图像的函数:
// plugins/mixin-utils.js
import Vue from 'vue'
Vue.mixin({
methods: {
loadAssetImage (src) {
return require('~/assets/images' + src)
}
}
})
在 Nuxt 配置文件中包含前面的全局混合路径:
// nuxt.config.js
module.exports = {
plugins: [
'~/plugins/mixin-utils.js'
]
}
现在,你可以在你的组件中随意使用loadAssetImage函数,如下所示:
// pages/about.vue
请注意,我们不需要像导入基本混入那样导入全局混入,因为我们已经通过nuxt.config.js全局注入了它们。但同样,要谨慎而谨慎地使用它们。
您可以在本书的 GitHub 存储库中的/chapter-5/nuxt-universal/mixins/global/中找到这个混入的示例。
混入非常有用。全局混入如全局 Vue 组件在数量过多时很难管理,因此会使您的应用难以预测和调试。因此,明智而谨慎地使用它们。我们希望您现在知道 Vue 组件是如何工作的以及如何编写它们。然而,仅仅知道它们是如何工作和如何编写它们是不够的 - 我们应该了解编写可读性和未来可管理性时需要遵守的标准规则。因此,在结束本章之前,我们将看一些这些规则。
定义组件名称和使用命名约定
在本章和前几章中,我们已经看到并创建了许多组件。我们创建的组件越多,我们就越需要遵循组件的命名约定。否则,我们将不可避免地会遇到混淆和错误,以及反模式。我们的组件将不可避免地会相互冲突 - 甚至与 HTML 元素相冲突。幸运的是,有一个官方的 Vue 风格指南,我们可以遵循以提高我们应用的可读性。在本节中,我们将介绍一些特定于本书的规则。
多词组件名称
我们现有和未来的 HTML 元素都是单词(例如article,main,body等),因此为了防止冲突发生,我们在命名组件时应该使用多个单词(除了根应用组件)。例如,以下做法被认为是不好的:
// .js
Vue.component('post', { ... })
// .vue
export default {
name: 'post'
}
组件的名称应该按照以下方式书写:
// .js
Vue.component('post-item', { ... })
// .vue
export default {
name: 'PostItem'
}
组件数据
我们应该始终使用data函数而不是data属性来处理组件的数据,除了在根 Vue 实例中。例如,以下做法被认为是不好的:
// .js
Vue.component('foo-component', {
data: { ... }
})
// .vue
export default {
data: { ... }
}
上述组件中的数据应该按照以下方式书写:
// .js
Vue.component('foo-component', {
data () {
return { ... }
}
})
// .vue
export default {
data () {
return { ... }
}
}
// .js or .vue
new Vue({
data: { ... }
})
但是为什么呢?这是因为当 Vue 初始化数据时,它会从vm.$options.data创建一个对data的引用。因此,如果数据是一个对象,并且一个组件有多个实例,它们都将使用相同的data。更改一个实例中的数据将影响其他实例。这不是我们想要的。因此,如果data是一个函数,Vue 将使用getData方法返回一个只属于当前初始化实例的新对象。因此,根实例中的数据在所有其他组件实例中共享,这些实例包含它们自己的数据。你可以通过this.$root.$data从任何组件实例中访问根数据。你可以在本书的 GitHub 存储库中的/chapter-5/vue/component-webpack/data/和/chapter-5/vue/data/basic.html中查看一些示例。
你可以在github.com/vuejs/vue/blob/dev/src/core/instance/state.js#L112上查看 Vue 源代码,了解数据是如何初始化的。
属性定义
我们应该在props属性中定义属性,以便尽可能详细地指定它们的类型(至少)。只有在原型设计时才可以不进行详细定义。例如,以下做法被认为是不好的:
props: ['message']
这应该这样写:
props: {
message: String
}
或者,更好的做法是这样写:
props: {
message: {
type: String,
required: false,
validator (value) { ... }
}
}
组件文件
我们应该始终遵守“一个文件一个组件”的政策;也就是说,一个文件中只写一个组件。这意味着你不应该在一个文件中有多个组件。例如,以下做法被认为是不好的:
// .js
Vue.component('PostList', { ... })
Vue.component('PostItem', { ... })
它们应该拆分成多个文件,如下所示:
components/
|- PostList.js
|- PostItem.js
如果你在.vue中编写组件,应该这样做:
components/
|- PostList.vue
|- PostItem.vue
单文件组件文件名大小写
我们应该只为单文件组件的文件名使用 PascalCase 或 kebab-case。例如,以下做法被认为是不好的:
components/
|- postitem.vue
components/
|- postItem.vue
它们应该这样写:
// PascalCase
components/
|- PostItem.vue
// kebab-case
components/
|- post-item.vue
自闭合组件
当我们的单文件组件中没有内容时,应该使用自闭合格式,除非它们在 DOM 模板中使用。例如,以下做法被认为是不好的:
// .vue
// .html
它们应该这样写:
// .vue
// .html
这些只是一些基本的规则。还有更多规则,比如编写多属性元素的规则,指令简写,带引号的属性值等等。但是我们在这里突出显示的选定规则应该足够让你完成本书。你可以在vuejs.org/v2/style-guide/找到其他规则和完整的样式指南。
摘要
干得好!在本章中,你学会了全局和局部 Vue 组件之间的区别,如何在 Nuxt 应用程序中注册全局组件,以及如何创建局部和全局 mixin。你还学会了如何通过props属性将数据传递给子组件,如何使用$emit方法从子组件向父组件发出数据,以及如何创建自定义输入组件。然后,你学会了为组件使用key属性的重要性。之后,你学会了如何使用 webpack 编写单文件组件。最后但同样重要的是,你了解了在 Nuxt 和 Vue 应用程序开发中应该遵循的一些规则。
在下一章中,我们将进一步探讨/plugins/目录的使用,通过编写 Vue 中的自定义插件并导入它们来扩展 Nuxt 应用程序。我们还将研究如何从 Vue 社区导入外部 Vue 插件,通过将它们注入到 Nuxt 的$root和context组件中创建全局函数,编写基本/异步模块和模块片段,并使用 Nuxt 社区的外部 Nuxt 模块。我们将会对这些进行详细的指导,敬请关注!
编写插件和模块
还记得自从第三章以来在 Nuxt 应用程序中编写一些简单的插件吗,添加 UI 框架?正如我们之前提到的,插件本质上是JavaScript 函数。在 web 开发中,您总是需要编写自定义函数以适应您的情况,在本书中我们将创建相当多的函数。在本章中,我们将更详细地了解为您的 Nuxt 应用程序创建自定义插件,以及自定义模块。您将学习在 Vue 应用程序中创建自定义插件并在 Nuxt 应用程序中实现它们。然后,您将学习如何在插件之上创建自定义 Nuxt 模块。您还将学习导入和安装现有的 Vue 插件和 Nuxt 模块,这些插件和模块是来自 Vue 和 Nuxt 社区的贡献,可以在您的 Nuxt 应用程序中使用。无论是自定义的还是外部导入的,学习和理解 Vue 插件和 Nuxt 模块都很重要,因为在接下来的章节中我们将经常使用其中的一些。
本章我们将涵盖以下主题:
编写 Vue 插件
在 Nuxt 中编写全局函数
编写 Nuxt 模块
编写异步 Nuxt 模块
编写 Nuxt 模块片段
第六章:编写 Vue 插件
插件是封装在.js文件中的全局 JavaScript 函数,可以通过使用Vue.use全局方法在应用程序中安装。我们在第四章的过去示例中使用了一些 Vue 插件,例如vue-router和vue-meta。这些插件必须在使用new语句初始化根 Vue 之前通过Vue.use方法安装,如下例所示:
// src/entry.js
import Vue from 'vue'
import Meta from 'vue-meta'
Vue.use(Meta)
new VueRouter({ ... })
您可以通过Vue.use将选项传递到插件中以配置插件的格式:
Vue.use(
例如,我们可以将以下选项传递到vue-meta插件中:
Vue.use(Meta, {
keyName: metaData, // default => metaInfo
refreshOnceOnNavigation: true // default => false
})
选项是可选的。这意味着您可以在不传递任何选项的情况下使用插件本身。Vue.use还可以防止您意外多次注入相同的插件,因此多次调用插件将只安装一次。
您可以查看awesome-vue,这是一个庞大的社区贡献的插件和库集合,网址为https://github.com/vuejs/awesome-vuecomponents--libraries。
现在让我们在下一节中探讨如何创建 Vue 插件。
在 Vue 中编写自定义插件
编写 Vue 插件相当容易。您只需要在插件中使用一个install方法来接受Vue作为第一个参数和options作为第二个参数:
// plugin.js
export default {
install (Vue, options) {
// ...
}
}
让我们为标准 Vue 应用程序创建一个简单的自定义问候插件,以不同的语言。可以通过options参数配置语言;当没有提供选项时,将使用英语作为默认语言:
在/src/目录中创建一个/plugins/文件夹,并在其中创建一个basic.js文件,其中包含以下代码:
// src/plugins/basic.js
export default {
install (Vue, options) {
if (options === undefined) {
options = {}
}
let { language } = options
let languages = {
'EN': 'Hello!',
'ES': 'Hola!'
}
if (language === undefined) {
language = 'EN'
}
Vue.prototype.$greet = (name) => {
return languages[language] + ' ' + name
}
Vue.prototype.$message = 'Helló Világ!'
}
}
在这个简单的插件中,我们还添加了一个名为$message的实例属性,其默认值为匈牙利语的“Hello World!”(Helló Világ!),当此插件在组件中使用时可以进行修改。请注意,{ language } = options是使用 ES6 编写language = options.language的方式。此外,我们应该使用$作为方法和属性的前缀,因为这是一种惯例。
安装和配置此插件如下:
// src/entry.js
import PluginSample from './plugins/basic'
Vue.use(PluginBasic, {
language: 'ES'
})
然后我们可以在任何 Vue 组件中全局使用该插件,就像以下示例中一样:
// src/components/home.vue
{{ $greet('John') }}
{{ $message }}
{{ messages }}
export default {
data () {
let helloWorld = []
helloWorld.push(this.$message)
this.$message = 'Ciao mondo!'
helloWorld.push(this.$message)
return { messages: helloWorld }
}
}
因此,当您在浏览器上运行应用程序时,您应该在屏幕上看到以下输出:
Hola! John
Ciao mondo!
[ "Helló Világ!", "Ciao mondo!" ]
您还可以在插件中使用component或directive,就像以下示例中一样:
// src/plugins/component.js
export default {
install (Vue, options) {
Vue.component('custom-component', {
// ...
})
}
}
// src/plugins/directive.js
export default {
install (Vue, options) {
Vue.directive('custom-directive', {
bind (el, binding, vnode, oldVnode) {
// ...
}
})
}
}
我们还可以使用Vue.mixin()将插件注入到所有组件中,如下所示:
// src/plugins/plugin-mixin.js
export default {
install (Vue, options) {
Vue.mixin({
// ...
})
}
}
您可以在我们的 GitHub 存储库的/chapter-6/vue/webpack/中找到前面的示例 Vue 应用程序。
就是这样。创建一个可以在 Vue 应用程序中安装和使用的 Vue 插件非常简单,不是吗?那么在 Nuxt 应用程序中呢?我们如何在 Nuxt 应用程序中安装前面的自定义 Vue 插件?让我们在下一节中找出答案。
将 Vue 插件导入到 Nuxt 中
在 Nuxt 应用程序中,该过程基本相同。所有插件都必须在初始化根 Vue 之前运行。因此,如果我们想要使用 Vue 插件,就像之前的示例插件一样,我们需要在启动 Nuxt 应用程序之前设置插件。让我们将我们的自定义basic.js插件复制到 Nuxt 应用程序的/plugins/目录中,然后执行以下步骤来安装它:
创建一个basic-import.js文件,以以下方式在/plugins/目录中导入basic.js:
// plugins/basic-import.js
import Vue from 'vue'
import PluginSample from './basic'
Vue.use(PluginSample)
这次在使用Vue.use方法安装插件时,我们跳过了选项。
将basic-import.js的文件路径添加到 Nuxt 配置文件的plugins选项中,如下所示:
export default {
plugins: [
'~/plugins/basic-import',
]
}
在任何喜欢的页面中使用此插件-就像我们在 Vue 应用程序中所做的那样:
// pages/index.vue
{{ $greet('Jane') }}
{{ $message }}
{{ messages }}
export default {
data () {
let helloWorld = []
helloWorld.push(this.$message)
this.$message = 'Olá Mundo!'
helloWorld.push(this.$message)
return { messages: helloWorld }
}
}
在浏览器上运行 Nuxt 应用程序,您应该在屏幕上看到以下输出:
Hello! Jane
Olá Mundo!
[ "Helló Világ!", "Olá Mundo!" ]
这次我们使用$greet方法得到了英文版的“Hello!”,因为在安装插件时没有设置任何语言选项。此外,在这个索引页面的块中,你将得到“Olá Mundo!”的$message,而在其他页面(例如/about、/contact)上,你将得到“Helló Világ!”,因为我们只在索引页面上设置了这个葡萄牙语版本的“Hello World!”,即this.$message = 'Olá Mundo!'。
正如我们在本章开头提到的,有一个庞大的社区贡献的 Vue 插件集合,可能对你的 Nuxt 应用程序有用,但是一些插件可能只在浏览器中工作,因为它们缺乏 SSR(服务器端渲染)支持。因此,在接下来的部分,我们将看看如何解决这种类型的插件。
导入没有 SSR 支持的外部 Vue 插件
在 Nuxt 中,有一些 Vue 插件已经预先安装好了,比如vue-router、vue-meta、vuex和vue-server-renderer。未安装的插件可以按照我们在上一节中安装自定义 Vue 插件的步骤轻松排序。以下是我们如何在 Nuxt 应用程序中使用vue-notifications的示例:
使用 npm 安装插件:
$ npm i vue-notification
导入并注入插件,就像我们使用自定义插件一样:
// plugins/vue-notifications.js
import Vue from 'vue'
import VueNotifications from 'vue-notifications'
Vue.use(VueNotifications)
将文件路径包含到 Nuxt 配置文件中:
// nuxt.config.js:
export default {
plugins: ['~/plugins/vue-notifications']
}
对于没有 SSR 支持的插件,或者当你只想在客户端上使用这个插件时,你可以在plugins选项中使用mode: 'client'选项,以确保这个插件不会在服务器端执行,就像下面的例子一样:
// nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/vue-notifications', mode: 'client' }
]
}
如你所见,安装 Vue 插件只需要三个步骤,无论是外部插件还是自定义插件。总之,Vue 插件是通过使用Vue.use方法将全局 JavaScript 函数注入到 Vue 实例中,并通过在插件内部暴露install方法来实现的。但在 Nuxt 本身中,还有其他创建全局函数的方法,可以将它们注入到 Nuxt 上下文(context)和 Vue 实例($root)中,而无需使用install方法。我们将在接下来的部分中探讨这些方法。
有关vue-notifications的更多信息,请访问https://github.com/euvl/vue-notification。
在 Nuxt 中编写全局函数
在 Nuxt 中,我们可以通过将它们注入到以下三个项目中来创建“插件”或全局函数:
Vue 实例(客户端):
// plugins/
import Vue from 'vue'
Vue.prototype.$
//...
}
Nuxt 上下文(服务器端):
// plugins/
export default (context, inject) => {
context.app.$
//...
}
}
Vue 实例和 Nuxt 上下文:
// plugins/
export default (context, inject) => {
inject('
//...
})
}
使用上述格式,你可以轻松地为你的应用编写全局函数。在接下来的章节中,我们将指导你通过一些示例函数。所以让我们开始吧。
将函数注入到 Vue 实例中
在这个例子中,我们将创建一个用于计算两个数字之和的函数,例如,1 + 2 = 3。我们将通过以下步骤将这个函数注入到 Vue 实例中:
创建一个.js文件,导入vue,并将函数附加到vue.prototype中的/plugins/目录中:
// plugins/vue-injections/sum.js
import Vue from 'vue'
Vue.prototype.$sum = (x, y) => x + y
将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js
export default {
plugins: ['~/plugins/vue-injections/sum']
}
在任何你喜欢的地方使用这个函数,例如:
// pages/vue-injections.vue
{{ this.$sum(1, 2) }}
{{ sum }}
export default {
data () {
return {
sum: this.$sum(2, 3)
}
}
}
在浏览器上运行页面,你应该在屏幕上得到以下输出(即使刷新页面):
3
5
将函数注入到 Nuxt 上下文中
在这个例子中,我们将创建一个用于计算一个数字的平方的函数,例如,5 * 5 = 25。我们将通过以下步骤将这个函数注入到 Nuxt 上下文中:
创建一个.js文件,并将函数附加到context.app中:
// plugins/ctx-injections/square.js
export default ({ app }, inject) => {
app.$square = (x) => x * x
}
将函数文件路径添加到 Nuxt 配置文件的plugins选项中:
// nuxt.config.js
export default {
plugins: ['~/plugins/ctx-injections/square']
}
在任何你喜欢的页面上使用这个函数,只要你可以访问到上下文,例如在asyncData方法中:
// pages/ctx-injections.vue
{{ square }}
export default {
asyncData (context) {
return {
square: context.app.$square(5)
}
}
}
在浏览器上运行页面,你应该在屏幕上得到以下输出(即使刷新页面):
25
请注意,asyncData总是在页面组件初始化之前调用,你不能在这个方法中访问this。因此,你不能在asyncData方法中使用你注入到 Vue 实例($root)中的函数,比如我们在前面例子中创建的$sum函数(我们将在第八章中更详细地了解asyncData)。同样,我们也不能在 Vue 的生命周期钩子/方法(例如mounted、updated等)中调用上下文注入的函数,比如这个例子中的$square函数。但是,如果你想要一个可以从this和context中使用的函数,让我们看看如何通过在下一节中将这种函数注入到 Vue 实例和 Nuxt 上下文中来实现。
将函数注入到 Vue 实例和 Nuxt 上下文中
在这个例子中,我们将创建一个用于计算两个数字之积的函数,例如,2 * 3 = 6。我们将通过以下步骤将这个函数注入到 Vue 实例和 Nuxt 上下文中:
创建一个.js文件,并使用inject函数封装您的函数:
// plugins/combined-injections/multiply.js
export default ({ app }, inject) => {
inject('multiply', (x, y) => x y)
}
请注意,$会自动添加到您的函数前缀,因此您不必担心将其添加到您的函数中。
将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js
export default {
plugins: ['~/plugins/combined-injections/multiply']
}
在任何您可以访问context和this(Vue 实例)的页面上使用该函数,例如以下示例:
// pages/combined-injections.vue
{{ this.$multiply(4, 3) }}
{{ multiply }}
export default {
asyncData (context) {
return { multiply: context.app.$multiply(2, 3) }
}
}
在浏览器上运行页面,您应该在屏幕上得到以下输出(即使在刷新页面时也是如此):
12
6
您可以在任何 Vue 生命周期钩子中使用该函数,例如以下示例:
mounted () {
console.log(this.$multiply(5, 3))
}
您应该在浏览器控制台上得到15的输出。此外,您还可以从Vuex store的actions和mutations对象/属性中访问该函数,我们将在第十章中介绍添加一个 Vuex Store。
创建一个.js文件,并将以下函数封装在actions和mutations对象中:
// store/index.js
export const state = () => ({
xNumber: 1,
yNumber: 3
})
export const mutations = {
changeNumbers (state, newValue) {
state.xNumber = this.$multiply(3, 8)
state.yNumber = newValue
}
}
export const actions = {
setNumbers ({ commit }) {
const newValue = this.$multiply(9, 6)
commit('changeNumbers', newValue)
}
}
在任何您喜欢的页面上使用前面的存储action方法,例如以下示例:
// pages/combined-injections.vue
{{ $store.state }}
export default {
methods: {
updateStore () {
this.$store.dispatch('setNumbers')
}
}
}
在浏览器上运行页面,您应该在屏幕上得到以下输出(即使在刷新页面时也是如此):
{ "xNumber": 1, "yNumber": 3 }
单击“更新存储”按钮,前面的数字将更改为存储默认状态如下:
{ "xNumber": 24, "yNumber": 54 }
这很棒。通过这种方式,我们可以编写一个在客户端和服务器端都能工作的插件。但有时,我们需要能够在服务器端或客户端独占地使用的函数。为了做到这一点,我们必须指示 Nuxt 如何专门运行我们的函数。让我们在下一节中找出如何做到这一点。
注入仅客户端或仅服务器端的插件
在这个例子中,我们将创建一个用于除法的函数,例如,8 / 2 = 4,以及另一个用于减法的函数,例如,8 - 2 = 6。我们将将第一个函数注入到 Vue 实例中,并使其专门用于客户端使用,而将第二个函数注入到 Nuxt 上下文中,并使其专门用于服务器端使用。
创建两个函数,并将它们分别附加.client.js和.server.js:
// plugins/name-conventions/divide.client.js
import Vue from 'vue'
Vue.prototype.$divide = (x, y) => x / y
// plugins/name-conventions/subtract.server.js
export default ({ app }, inject) => {
inject('subtract', (x, y) => x - y)
}
附加.client.js的函数文件将仅在客户端运行,而附加.server.js的函数文件将仅在服务器端运行。
将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js:
export default {
plugins: [
'~/plugins/name-conventions/divide.client.js',
'~/plugins/name-conventions/subtract.server.js'
]
}
在任何你喜欢的页面上使用这些插件,比如以下示例:
// pages/name-conventions.vue
{{ divide }}
{{ subtract }}
export default {
data () {
let divide = ''
if (process.client) {
divide = this.$divide(8, 2)
}
return { divide }
},
asyncData (context) {
let subtract = ''
if (process.server) {
subtract = context.app.$subtract(10, 4)
}
return { subtract }
}
}
在浏览器上运行页面,你应该在屏幕上得到以下输出:
4
6
请注意,当你在浏览器上首次运行页面时,你将得到前面的结果,即使在刷新页面时也是如此。但是在第一次加载后,如果你通过
4
另外,请注意我们必须将$divide方法包裹在process.client的 if 条件中,因为它是一个只在客户端执行的函数。如果你移除process.client的 if 条件,你将在浏览器中得到一个服务器端错误:
this.$divide is not a function
对于$subtract方法也是一样的:我们必须将其包裹在process.server的 if 条件中,因为它是一个只在服务器端执行的函数。如果你移除process.server的 if 条件,你将在浏览器上得到一个客户端错误:
this.$subtract is not a function
将函数包裹在process.server中可能不是理想的做法
process.client的 if 条件每次使用时都会被阻塞。但是在仅在客户端被调用的 Vue 生命周期钩子/方法上,比如mounted钩子,你不需要使用process.client的 if 条件。因此,你可以在不使用 if 条件的情况下安全地使用你的仅客户端函数,就像以下示例中一样:
mounted () {
console.log(this.$divide(8, 2))
}
你将在浏览器控制台中得到4的输出。下表显示了八个 Vue 生命周期钩子/方法,值得知道的是在 Nuxt 应用中只有其中两个会在两端被调用:
服务器和客户端
仅客户端
|
beforeCreate ()
created ()
|
beforeMount ()
mounted ()
beforeUpdate ()
updated ()
beforeDestroy ()
destroyed ()
|
请注意,我们在 Vue 和 Nuxt 应用中一直在使用的data方法会在两端被调用,就像asyncData方法一样。因此,你可以在仅客户端列表下的钩子中使用$divide方法,它是专门为客户端使用而制作的,而不需要 if 条件。而对于$subtract方法,它是专门为仅在服务器端使用而制作的,你可以在nuxtServerInit动作中安全地使用它,就像以下示例中一样:
export const actions = {
nuxtServerInit ({ commit }, context) {
console.log(context.app.$subtract(10, 4))
}
}
当您的应用在服务器端运行时,即使刷新页面(任何页面),您将得到6的输出。值得知道的是,只能通过这些方法访问 Nuxt 上下文:nuxtServerInit和asyncData。nuxtServerInit操作可以作为第二个参数访问上下文,而asyncData方法可以作为第一个参数访问上下文。我们将在第十章中介绍nuxtServerInit操作,添加一个 Vuex Store,但是,现在在下一节中,我们将看一下在nuxtServerInit操作之后,但在 Vue 实例和插件之前以及在$root和 Nuxt 上下文注入的函数之前注入到 Nuxt 上下文中的 JavaScript 函数。这种类型的函数称为 Nuxt 模块,通过本章末尾,您将知道如何编写这些模块。让我们开始吧。
编写 Nuxt 模块
模块只是一个顶级的 JavaScript 函数,在 Nuxt 启动时执行。Nuxt 会按顺序调用每个模块,并在继续调用 Vue 实例、Vue 插件和要注入到$root和 Nuxt 上下文中的全局函数之前等待所有模块完成。因为模块在它们之前被调用(即 Vue 实例等),我们可以使用模块来覆盖模板、配置 webpack 加载器、添加 CSS 库以及执行其他应用所需的任务。此外,模块也可以打包成 npm 包并与 Nuxt 社区共享。您可以查看以下链接,了解由 Nuxt 社区制作的生产就绪模块:
github.com/nuxt-community/awesome-nuxt#official
让我们试试 Axios 模块,这是一个与 Axios 集成的模块(github.com/axios/axios)用于 Nuxt。它具有一些功能,例如自动设置客户端和服务器端的基本 URL。我们将在接下来的章节中发现它的一些特性。如果您想了解更多关于这个模块的信息,请访问axios.nuxtjs.org/。现在,让我们看看如何在以下步骤中使用这个模块:
使用 npm 安装它:
$ npm install @nuxtjs/axios
在 Nuxt 配置文件中进行配置:
// nuxt.config.js
module.exports = {
modules: [
'@nuxtjs/axios'
]
}
在任何地方使用,例如在页面的asyncData方法中:
// pages/index.vue
async asyncData ({ $axios }) {
const ip = await $axios.$get('http://icanhazip.com')
console.log(ip)
}
您还可以在mounted方法(或created,updated等)中使用它,如下所示:
// pages/index.vue
async mounted () {
const ip = await this.$axios.$get('http://icanhazip.com')
console.log(ip)
}
每次导航到/about页面时,您应该在浏览器控制台上看到您的 IP 地址。您应该注意到现在您可以像使用原始 Axios 一样发送 HTTP 请求,而无需在需要时导入它,因为它现在通过模块全局注入。很棒,不是吗?接下来,我们将通过从基本模块开始编写您的模块来指导您。
编写基本模块
正如我们已经提到的,模块是函数,它们可以选择地打包为 npm 模块。这是您创建模块所需的非常基本的结构:
// modules/basic.js
export default function (moduleOptions) {
// ....
}
您只需在项目根目录中创建一个/modules/目录,然后开始编写您的模块代码。请注意,如果您想将模块发布为 npm 包,必须包含以下行:
module.exports.meta = require('./package.json')
如果您想创建模块并将其发布为 npm 包,请按照 Nuxt 社区的此模板:
github.com/nuxt-community/module-template/tree/master/template
无论您是为 Nuxt 社区还是仅为自己的项目创建模块,每个模块都可以访问以下内容:
模块选项:
我们可以从配置文件中向模块传递 JavaScript 对象中的一些选项,例如:
// nuxt.config.js
export default {
modules: [
['~/modules/basic/module', { language: 'ES' }],
]
}
然后,您可以在模块函数的第一个参数中将前述选项作为moduleOptions访问,如下所示:
// modules/basic/module.js
export default function (moduleOptions) {
console.log(moduleOptions)
}
您将获得从配置文件中传递的以下选项:
{
language: 'ES'
}
配置选项:
我们还可以创建一个自定义选项(例如token,proxy或basic),并将一些特定选项传递给模块(这个自定义选项可以在模块之间共享使用),如下例所示:
// nuxt.config.js
export default {
modules: [
['~/modules/basic/module'],
],
basic: { // custom option
option1: false,
option2: true,
}
}
然后,您可以使用this.options访问前述自定义选项,如下所示:
// modules/basic/module.js
export default function (moduleOptions) {
console.log(this.options['basic'])
}
您将获得从配置文件中传递的以下选项:
{
option1: false,
option2: true
}
然后我们可以将moduleOptions和this.options组合如下:
// modules/basic/module.js
export default function (moduleOptions) {
const options = {
...this.options['basic'],
...moduleOptions
}
console.log(options)
}
您将获得从配置文件中传递的以下组合选项:
{
option1: false,
option2: true
}
Nuxt 实例:
您可以使用this.nuxt来访问 Nuxt 实例。请访问以下链接以获取可用方法(例如hook方法,我们可以使用它在 Nuxt 启动时创建特定事件的某些任务):
nuxtjs.org/api/internals-nuxt
ModuleContainer实例:
您可以使用 this 来访问 ModuleContainer 实例。请访问以下链接以获取可用方法(例如,addPlugin 方法,我们在模块中经常使用它来注册插件):
nuxtjs.org/api/internals-module-container
module.exports.meta 代码行:
如果您将您的模块发布为 npm 包,则此行是必需的,正如我们之前提到的。但在本书中,我们将指导您完成为您的项目创建模块的步骤。让我们通过以下步骤开始创建一个非常基本的模块:
创建一个带有以下代码的 module 文件:
// modules/basic/module.js
const { resolve } = require('path')
export default function (moduleOptions) {
const options = {
...this.options['basic'],
...moduleOptions
}
// Add plugin.
this.addPlugin({
src: resolve(__dirname, 'plugin.js'),
fileName: 'basic.js',
options
})
}
创建一个带有以下代码的 plugin 文件:
// modules/basic/plugin.js
var options = []
<% if (options.option1 === true) { %>
options.push('option 1')
<% } %>
<% if (options.option2 === true) { %>
options.push('option 2')
<% } %>
<% if (options.language === 'ES') { %>
options.push('language ES')
<% } %>
const basic = function () {
return options
}
export default ({ app }, inject) => {
inject('basic', basic)
}
请注意,<%= %> 符号是 Lodash 用于在 template 函数中插入数据属性的插值分隔符。我们稍后将在本章再次介绍它们。有关 Lodash template 函数的更多信息,请访问 lodash.com/docs/4.17.15#template。
仅在 Nuxt 配置文件中包含模块文件路径(/modules/basic/module.js),并提供一些选项,如下所示使用 basic 自定义选项:
// nuxt.config.js
export default {
modules: [
['~/modules/basic/module', { language: 'ES' }],
],
basic: {
option1: false,
option2: true,
}
}
您可以在任何地方使用它,例如:
// pages/index.vue
mounted () {
const basic = this.$basic()
console.log(basic)
}
每次访问主页时,您应该在浏览器控制台上看到以下输出:
["option 2", "language ES"]
请注意 module.js 如何处理高级配置细节,例如语言和选项。它还负责注册 plugin.js 文件,该文件执行实际工作。正如您所看到的,该模块是围绕插件的一个包装器。我们将在接下来的章节中更详细地学习这一点。
请注意,如果您只为构建时间和开发编写模块,则应在 Nuxt 配置文件中使用 buildModules 选项来注册您的模块,而不是在 Node.js 运行时使用 modules 选项。有关此选项的更多信息,请访问 nuxtjs.org/guide/modules#build-only-modules 和 nuxtjs.org/api/configuration-modules。
编写异步 Nuxt 模块
如果您需要在模块中使用 Promise 对象,例如,使用 HTTP 客户端从远程 API 获取一些异步数据,那么 Nuxt 可以完美支持。以下是一些选项,您可以使用这些选项编写您的 async 模块。
使用 async/await
您可以在您的模块中使用 ES6 的 async/await 与 Axios,这是我们自第四章以来一直在使用的 HTTP 客户端,例如以下示例中:
// modules/async-await/module.js
import axios from 'axios'
export default async function () {
let { data } = await axios.get(
'https://jsonplaceholder.typicode.com/posts')
let routes = data.map(post => '/posts/' + post.id)
console.log(routes)
}
// nuxt.config.js
modules: [
['~/modules/async-await/module']
]
在前面的例子中,我们使用 Axios 的get方法从远程 API JSONPlaceholder(jsonplaceholder.typicode.com/)获取所有帖子。当您第一次启动 Nuxt 应用程序时,您应该在终端上看到以下输出:
[
'/posts/1',
'/posts/2',
'/posts/3',
...
]
返回一个 Promise
您可以在您的模块中使用 promise 链并返回Promise对象,就像以下示例中一样:
// modules/promise-sample/module.js
import axios from 'axios'
export default function () {
return axios.get('https://jsonplaceholder.typicode.com/comments')
.then(res => res.data.map(comment => '/comments/' + comment.id))
.then(routes => {
console.log(routes)
})
}
// nuxt.config.js
modules: [
['~/modules/promise-sample/module']
]
在这个例子中,我们使用 Axios 的get方法从远程 API 获取所有评论。然后我们使用then方法来链Promise 并打印结果。当您第一次启动 Nuxt 应用程序时,您应该在终端上看到以下输出:
[
'/comments/1',
'/comments/2',
'/comments/3',
...
]
您可以在我们的 GitHub 存储库的/chapter-6/nuxt-universal/modules/async/中找到这两个示例。
有了这两个异步选项和您从前面部分学到的基本模块编写技能,您可以轻松开始创建您的 Nuxt 模块。我们将在下一节中通过编写模块的小片段来查看更多示例 - 片段。
编写 Nuxt 模块片段
在这个主题中,我们将把我们创建的自定义模块分解成小片段。
您可以在我们的 GitHub 存储库的/chapter-6/nuxt-universal/module-snippets/中找到所有以下代码。
使用顶级选项
记住我们在编写基本模块部分中说过的可以传递到模块中的配置选项吗?模块选项是在 Nuxt 配置文件中注册我们的模块的顶级选项。我们甚至可以结合来自不同模块的多个选项,并且它们的选项可以共享。让我们尝试在以下步骤中一起使用@nuxtjs/axios和@nuxtjs/proxy的示例:
使用 npm 一起安装这两个模块:
$ npm i @nuxtjs/axios
$ npm i @nuxtjs/proxy
这两个模块被很好地集成在一起,以防止 CORS 问题,我们将在本书的后面看到并讨论跨域应用程序的开发。不需要手动注册@nuxtjs/proxy模块,但它确实需要在您的package.json文件的依赖项中。
在 Nuxt 配置文件中注册@nuxtjs/axios模块并设置这两个模块的顶级选项:
// nuxt.config.js
export default {
modules: [
'@nuxtjs/axios'
],
axios: {
proxy: true
},
proxy: {
'/api/': { target: 'https://jsonplaceholder.typicode.com/',
pathRewrite: {'^/api/': ''} },
}
}
axios 自定义选项中的 proxy: true 选项告诉 Nuxt 使用 @nuxtjs/proxy 模块作为代理。proxy 自定义选项中的 /api/: {...} 选项告诉 @nuxtjs/axios 模块将 jsonplaceholder.typicode.com/ 作为 API 服务器的目标地址,而 pathRewrite 选项告诉 @nuxtjs/axios 模块在 HTTP 请求期间从地址中删除 /api/,因为目标 API 中没有带有 /api 的路由。
接下来,在任何组件中无缝使用它们,就像以下示例中一样:
// pages/index.vue
export default {
async asyncData({ $axios }) {
const users = await $axios.$get('/api/users')
return { users }
}
}
现在,使用这两个模块,我们可以在请求方法(例如 get、post 和 put)中只写更短的 API 地址,比如 /api/users 而不是 https://jsonplaceholder.typicode.com/users。这样可以使我们的代码更整洁,因为我们不必在每次调用时都写完整的 URL。请注意,我们在 Nuxt 配置文件中配置的 /api/ 地址将被添加到对 API 端点的所有请求中。因此,我们使用 pathRewrite,如我们已经解释的那样,在发送请求时删除它。
你可以在以下链接中找到这两个模块提供的更多信息和顶层选项:
axios.nuxtjs.org/options 用于 @nuxtjs/axios
github.com/nuxt-community/proxy-module 用于 @nuxtjs/proxy
你可以在我们的 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/top-level/ 中找到我们刚创建的示例模块片段。
使用 addPlugin 辅助函数
还记得我们在 编写基本模块 部分介绍过的 ModuleContainer 实例和 this.addPlugin 辅助方法吗?在这个示例中,我们将使用这个辅助函数创建一个模块,该模块通过这个辅助函数提供了一个插件,这个插件就是 bootstrap-vue,它将被注册到 Vue 实例中。让我们按照以下步骤创建这个模块片段:
安装 Bootstrap 和 BootstrapVue:
$ npm i bootstrap-vue
$ npm i bootstrap
创建一个插件文件来导入 vue 和 bootstrap-vue,然后使用 use 方法注册 bootstrap-vue:
// modules/bootstrap/plugin.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'
Vue.use(BootstrapVue)
创建一个模块文件,使用 addPlugin 方法添加我们刚创建的插件文件:
// modules/bootstrap/module.js
import path from 'path'
export default function (moduleOptions) {
this.addPlugin(path.resolve(__dirname, 'plugin.js'))
}
在 Nuxt 配置文件中添加这个 bootstrap 模块的文件路径:
// nuxt.config.js
export default {
modules: [
['~/modules/bootstrap/module']
]
}
在任何喜欢的组件上开始使用 bootstrap-vue;例如,让我们创建一个按钮来切换 Bootstrap 中的警报文本,如下所示:
// pages/index.vue
{{ show ? 'Hide' : 'Show' }} Alert
Hello {{ name }}!
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
data () {
return {
name: 'BootstrapVue',
show: true
}
}
}
有了这个模块片段,我们不必每次在组件上需要时导入bootstrap-vue,因为它已经通过前面的片段模块全局添加了。我们只需要导入它的 CSS 文件。在使用示例中,我们使用 Bootstrap 的自定义
有关 BootstrapVue 的更多信息,请访问bootstrap-vue.js.org/。您可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/provide-plugin/中找到我们刚刚创建的示例模块片段。
使用 Lodash 模板
再次,这是我们在编写基本模块部分创建的自定义模块中熟悉的内容-利用 Lodash 模板通过使用 if 条件块来改变注册插件的输出。再次,Lodash 模板是一段代码,我们可以用<%= %>插值分隔符插入数据属性。让我们在以下步骤中尝试另一个简单的例子:
创建一个插件文件来导入axios并添加 if 条件块,以确保为axios提供请求 URL,并在您的 Nuxt 应用程序以dev模式(npm run dev)运行时在终端上打印请求结果以进行调试:
// modules/users/plugin.js
import axios from 'axios'
let users = []
<% if (options.url) { %>
users = axios.get('<%= options.url %>')
<% } %>
<% if (options.debug) { %>
// Dev only code
users.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
})
<% } %>
export default ({ app }, inject) => {
inject('getUsers', async () => {
return users
})
}
创建一个module文件,使用addPlugin方法添加我们刚刚创建的插件文件,使用options选项传递请求 URL 和this.options.dev的布尔值给这个插件:
// modules/users/module.js
import path from 'path'
export default function (moduleOptions) {
this.addPlugin({
src: path.resolve(__dirname, 'plugin.js'),
options: {
url: 'https://jsonplaceholder.typicode.com/users',
debug: this.options.dev
}
})
}
将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
modules: [
['~/modules/users/module']
]
}
在任何您喜欢的组件上开始使用$getUsers方法,就像以下示例中一样:
// pages/index.vue
export default {
async asyncData({ app }) {
const { data: users } = await app.$getUsers()
return { users }
}
}
在上面的示例中,Nuxt 将在将插件复制到项目时将options.url替换为https://jsonplaceholder.typicode.com/users。options.debug的 if 条件块将在生产构建时从插件代码中剥离,因此您在生产模式(npm run build和npm run start)中将看不到终端上的console.log输出。
您可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/template-plugin/中找到我们刚刚创建的示例模块片段。
添加 CSS 库
在使用 addPlugin 助手部分的模块片段示例中,我们创建了一个模块,允许我们在应用程序中全局使用bootstrap-vue插件,而无需使用import语句来引入此插件,如下例所示:
// pages/index.vue
{{ show ? 'Hide' : 'Show' }} Alert
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
//...
}
这看起来非常整洁,因为我们不必每次都导入bootstrap-vue,而只需导入 CSS 样式即可。但是,通过模块,我们仍然可以节省几行代码,将样式添加到应用程序的全局 CSS 堆栈中。让我们创建一个新的示例,并看看我们如何在以下步骤中执行该操作:
创建一个模块文件,其中包含一个名为options的const变量,用于将模块和顶层选项传递给插件文件,以及一个 if 条件块,用于确定是否使用原始 JavaScript 的push方法将 CSS 文件推送到 Nuxt 配置文件中的css选项中:
// modules/bootstrap/module.js
import path from 'path'
export default function (moduleOptions) {
const options = Object.assign({}, this.options.bootstrap,
moduleOptions)
if (options.styles !== false) {
this.options.css.push('bootstrap/dist/css/bootstrap.css')
this.options.css.push('bootstrap-vue/dist/bootstrap-vue.css')
}
this.addPlugin({
src: path.resolve(__dirname, 'plugin.js'),
options
})
}
创建一个插件文件,其中注册了bootstrap-vue插件,并使用 if 条件 Lodash-template 块打印从模块文件处理的选项:
// modules/bootstrap/plugin.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'
Vue.use(BootstrapVue)
<% if (options.debug) { %>
<% console.log (options) %>
<% } %>
将模块文件的文件路径添加到 Nuxt 配置文件中,模块选项指定是否在模块文件中禁用 CSS 文件。还要添加顶层选项bootstrap,以将布尔值传递给debug选项:
// nuxt.config.js
export default {
modules: [
['~/modules/bootstrap/module', { styles: true }]
],
bootstrap: {
debug: process.env.NODE_ENV === 'development' ? true : false
}
}
从我们的组件中删除 CSS 文件:
// pages/index.vue
- import 'bootstrap/dist/css/bootstrap.css'
- import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
//...
}
因此,最终,我们可以在组件中使用bootstrap-vue插件及其 CSS 文件,而无需全部导入它们。以下是将 Font Awesome 的css选项快速推送到 Nuxt 配置文件的模块片段的另一个示例:
// modules/bootstrap/module.js
export default function (moduleOptions) {
if (moduleOptions.fontAwesome !== false) {
this.options.css.push('font-awesome/css/font-awesome.css')
}
}
如果您想了解有关 Font Awesome 的更多信息,请访问fontawesome.com/。
您可以在我们的 GitHub 存储库的/chapter-6/nuxt-universal/module-snippets/css-lib/中找到我们刚刚创建的示例模块片段。
注册自定义 webpack 加载器
当我们想要在 Nuxt 中扩展 webpack 配置时,通常会在nuxt.config.js中使用build.extend来完成。但是,我们也可以通过使用this.extendBuild和以下模块/加载器模板来通过模块执行相同的操作:
export default function (moduleOptions) {
this.extendBuild((config, { isClient, isServer }) => {
//...
})
}
例如,假设我们想要使用svg-transform-loader扩展我们的 webpack 配置,这是一个用于添加或修改 SVG 图像中标记和属性的 webpack 加载器。该加载器的主要目的是允许我们在 SVG 图像上使用fill、stroke和其他操作。我们还可以在 CSS、Sass、Less、Stylus 或 PostCSS 中使用它;例如,如果您想要用白色填充 SVG 图像,可以使用fill将fff(CSS 颜色白色代码)添加到图像中,如下所示:
.img {
background-image: url('./img.svg?fill=fff');
}
如果您想要在 Sass 中使用变量stroke SVG 图像,可以这样做:
$stroke-color: fff;
.img {
background-image: url('./img.svg?stroke={$stroke-color}');
}
让我们创建一个示例模块,将此加载器注册到 Nuxt webpack 默认配置中,以便我们可以通过以下步骤在 Nuxt 应用程序中操作 SVG 图像:
使用 npm 安装加载器:
$ npm i svg-transform-loader
使用我们之前提供的模块/加载器模板创建一个模块文件,如下所示:
// modules/svg-transform-loader/module.js
export default function (moduleOptions) {
this.extendBuild((config, { isClient, isServer }) => {
//...
})
}
在this.extendBuild的回调函数中,添加以下行以查找文件加载器并从其现有规则测试中删除svg:
const rule = config.module.rules.find(
r => r.test.toString() === '/\\.(png|jpe?g|gif|svg|webp)$/i'
)
rule.test = /\.(png|jpe?g|gif|webp)$/i
在前面的代码块之后添加以下代码块,将svg-transform-loader加载器推入默认 webpack 配置的模块规则:
config.module.rules.push({
test: /\.svg(\?.)?$/, // match img.svg and img.svg?param=value
use: [
'url-loader',
'svg-transform-loader'
]
})
模块现在已经完成,我们可以继续步骤 5。
将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
modules: [
['~/modules/svg-transform-loader/module']
]
}
开始转换我们组件中的任何 SVG 图像,例如以下内容:
// pages/index.vue
.background {
height: 100px;
width: 100px;
border: 4px solid red;
background-image: url('~assets/bug.svg?stroke=red&stroke-
width=2');
}
您可以在www.npmjs.com/package/svg-transform-loader找到有关svg-transform-loader的更多信息。如果您想了解有关规则测试的更多信息,并查看 Nuxt 默认 webpack 配置的完整内容,请访问以下链接:
webpack.js.org/configuration/module/ruletest webpack 规则测试
github.com/nuxt/nuxt.js/blob/dev/packages/webpack/src/config/base.js Nuxt 默认 webpack 配置
您可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/webpack-loader/中找到我们刚刚创建的示例模块片段。
注册自定义 webpack 插件
Nuxt 模块不仅允许我们注册 webpack 加载器,还允许我们使用以下模块/插件架构注册 webpack 插件:this.options.build.plugins.push。
export default function (moduleOptions) {
this.options.build.plugins.push({
apply(compiler) {
compiler.hooks.
//...
})
}
})
}
使用我们提供的模块/插件架构创建一个模块文件,以打印“Hello World!”,如下所示:
// modules/hello-world/module.js
export default function (moduleOptions) {
this.options.build.plugins.push({
apply(compiler) {
compiler.hooks.done.tap('HelloWordPlugin', (stats) => {
console.log('Hello World!')
})
}
})
}
请注意,在done挂钩被触发时,stats(统计信息)被传递为参数。
将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
modules: [
['~/modules/hello-world/module']
}
使用$ npm run dev运行你的 Nuxt 应用程序,你应该在终端上看到“Hello World!”。
请注意,apply方法,compiler,hooks和tap都是构建 webpack 插件的关键部分。
如果你是新手 webpack 插件开发者,并想了解更多关于如何为 webpack 开发插件,请访问webpack.js.org/contribute/writing-a-plugin/。
你可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/webpack-plugin/中找到我们刚刚创建的示例模块片段。
在特定挂钩上创建任务
如果你需要在 Nuxt 启动时对特定生命周期事件(例如,当所有模块加载完成时)执行某些任务,你可以创建一个模块,并使用hook方法来监听该事件,然后执行任务。请考虑以下示例:
如果你想在所有模块加载完成后做一些事情,请尝试以下操作:
export default function (moduleOptions) {
this.nuxt.hook('modules:done', moduleContainer => {
//...
})
}
如果你想在渲染器创建后做一些事情,请尝试以下操作:
export default function (moduleOptions) {
this.nuxt.hook('render:before', renderer => {
//...
})
}
如果你想在编译器(webpack 是默认值)启动之前做一些事情,请尝试以下操作:
export default function (moduleOptions) {
this.nuxt.hook('build:compile', async ({ name, compiler }) => {
//...
})
}
如果你想在 Nuxt 生成页面之前做一些事情,请尝试以下操作:
export default function (moduleOptions) {
this.nuxt.hook('generate:before', async generator => {
//...
})
}
如果你想在 Nuxt 准备就绪时做一些事情,请尝试以下操作:
export default function (moduleOptions) {
this.nuxt.hook('ready', async nuxt => {
//...
})
}
让我们按照以下步骤创建一个简单的模块来监听modules:done挂钩/事件:
创建一个模块文件,在所有模块加载完成时打印'All modules are loaded':
// modules/tasks/module.js
export default function (moduleOptions) {
this.nuxt.hook('modules:done', moduleContainer => {
console.log('All modules are loaded')
})
}
创建几个模块来打印'Module 1','Module 2','Module 3'等,如下所示:
// modules/module1.js
export default function (moduleOptions) {
console.log('Module 1')
}
将挂钩模块的文件路径和其他模块添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
modules: [
['~/modules/tasks/module'],
['~/modules/module3'],
['~/modules/module1'],
['~/modules/module2']
]
}
使用$ npm run dev运行你的 Nuxt 应用程序,你应该在终端上看到以下输出:
Module 3
Module 1
Module 2
All modules are loaded
你可以看到挂钩模块总是最后打印,而其余的根据它们在modules选项中的顺序打印。
挂钩模块可以是异步的,无论你是使用async/await函数还是返回Promise。
有关上述钩子和 Nuxt 生命周期事件中的其他钩子的更多信息,请访问以下链接:
模块容器内部钩子 用于 Nuxt 的模块生命周期事件(ModuleContainer类)
构建器内部钩子 用于 Nuxt 的构建生命周期事件(Builder类)
生成器内部钩子 用于 Nuxt 的生成生命周期事件(Generator类)
渲染器内部钩子 用于 Nuxt 的渲染生命周期事件(Renderer类)
Nuxt 内部钩子 用于 Nuxt 本身的生命周期事件(Nuxt类)
您可以在我们的 GitHub 存储库的/chapter-6/nuxt-universal/module-snippets/hooks/中找到我们刚刚创建的示例模块片段。
总结
在本章中,我们已成功涵盖了 Nuxt 中的插件和模块。您已经了解到它们在技术上是您可以为项目创建的 JavaScript 函数,或者从外部来源导入它们。此外,您已经学会了通过将它们注入到 Vue 实例或 Nuxt 上下文中(或两者都有)来为您的 Nuxt 应用创建全局函数,以及创建仅客户端和仅服务器端的函数。最后,您已经学会了通过使用addPlugin助手添加 JavaScript 库的模块片段,全局添加 CSS 库,使用 Lodash 模板有条件地更改已注册插件的输出,向 Nuxt 默认 webpack 配置添加 webpack 加载器和插件,以及使用 Nuxt 生命周期事件钩子创建任务,例如modules:done。
在接下来的章节中,我们将探索 Vue 表单并将其添加到 Nuxt 应用程序中。您将了解v-model在 HTML 元素(如text、textarea、checkbox、radio和select)中的工作原理。您将学会如何在 Vue 应用程序中验证这些元素,绑定默认和动态数据,并使用.lazy和.trim等修饰符来修改或强制输入值。您还将学会使用 Vue 插件vee-validate对它们进行验证,然后将其应用到 Nuxt 应用程序中。我们将引导您顺利地完成所有这些领域。敬请关注。
添加 Vue 表单
在本章中,您将使用v-model和v-bind创建表单。您将学习在将表单数据发送到服务器之前在客户端验证表单。您将创建具有基本元素的表单,绑定动态值,并使用修饰符修改输入元素的行为。您还将学习如何使用vee-validate插件验证表单并将其应用于 Nuxt 应用程序。在本章中学习如何在 Vue 表单中使用v-model和v-bind非常重要,因为我们将在接下来的章节中使用表单,例如在“添加 Vuex 存储”第十章和“创建用户登录和 API 身份验证”第十二章中。
本章中我们将涵盖以下主题:
理解v-model
使用基本数据绑定验证表单
创建动态值绑定
使用vee-validate验证表单
在 Nuxt 中应用 Vue 表单
第七章:理解v-model
v-model是 Vue 指令(自定义内置 Vue HTML 属性),允许我们在表单的input、textarea和select元素上创建双向绑定。您可以将表单输入与 Vue 数据绑定,以便在用户与输入字段交互时更新数据。v-model始终会跳过您在表单元素上设置的初始值,而将 Vue 数据视为真相的来源。因此,您应该在 Vue 端,在data选项或函数内声明初始值。
v-model将根据输入类型选择适当的方式来更新元素,这意味着如果您在type="text"的表单输入上使用它,它将使用value作为属性,并使用input作为事件来执行双向绑定。让我们看看在接下来的部分中包括哪些内容。
在文本和文本区域元素中使用 v-model
记得我们在《添加 Vue 组件》的第五章中使用v-model实现双向绑定来创建自定义输入组件吗?在该章节的“创建自定义输入组件”部分,我们学到了输入框的v-model语法 - - 实际上是以下内容的简写:
v-bind:value="username"
v-on:input="username = $event.target.value"
>
这个文本input元素在幕后绑定了value属性,该属性从处理程序username中获取值,而username又从input事件中获取值。因此,自定义的文本输入组件也必须始终在model属性中使用value属性和input事件,如下所示:
Vue.component('custom-input', {
props: {
value: String
},
model: {
prop: 'value',
event: 'input'
},
template: ``,
})
这仅仅是因为v-model输入的性质是由v-bind:value和v-on:input组成。当在textarea元素中使用v-model指令时,情况也是一样的,如下例所示:
这个v-model textarea元素是以下内容的简写:
这个textarea输入元素在幕后绑定了value属性,该属性从处理程序message中获取值,而message又从input事件中获取值。因此,自定义的textarea组件也必须始终遵守v-model textarea元素的性质,通过使用value属性和input事件在model属性中,如下所示:
Vue.component('custom-textarea', {
props: {
value: null
},
model: {
prop: 'value',
event: 'input'
}
})
简而言之,v-model文本input元素和v-model textarea输入元素始终将value属性与处理程序绑定,以在输入事件上获取新值,因此自定义输入组件也必须采用相同的属性和事件。那么复选框和单选按钮元素中的v-model又是怎样的呢?让我们在下一节中深入了解它们。
在复选框和单选按钮元素中使用 v-model
另一方面,v-model复选框和单选按钮输入元素始终将checked属性与在change事件上更新的布尔值绑定,如下例所示:
在上面的代码片段中,v-model checkbox输入元素确实是以下内容的简写:
type="checkbox"
name="subscribe"
value="yes"
v-bind:checked="false"
v-on:change="subscribe = $event.target.checked"
>
因此,自定义的复选框输入元素也必须始终遵守v-model复选框输入元素的性质(如前面的代码块中所示),通过在model属性中采用checked属性和change事件,如下所示:
Vue.component('custom-checkbox', {
props: {
checked: Boolean,
},
model: {
prop: 'checked',
event: 'change'
}
})
同样适用于v-model单选按钮输入元素,如下所示:
前面的v-model元素是以下内容的另一种简写:
type="radio"
name="answer"
value="yes"
v-bind:checked="answer == 'yes'"
v-on:change="answer = $event.target.value"
>
因此,自定义的单选按钮输入元素也必须始终遵守v-model元素的性质,如下所示:
Vue.component('custom-radio', {
props: {
checked: String,
value: String
},
model: {
prop: 'checked',
event: 'change'
}
})
简而言之,v-model、checkbox 和 radio 按钮输入元素总是绑定 value 属性,并在 change 事件上更新,因此自定义输入组件也必须采用相同的属性和事件。现在,让我们看看 v-model 在下一节中如何在 select 元素中工作。
在选择元素中使用 v-model
毫不奇怪,v-model select 输入元素总是将 value 属性与在 change 事件上获取其选定值的处理程序绑定,如下例所示:
前面的 v-model checkbox 输入元素只是以下内容的另一种简写:
因此,自定义的 checkbox 输入元素也必须始终遵守 v-model 元素的特性,使用 value 属性和 model 属性中的 change 事件,如下所示:
Vue.component('custom-select', {
props: {
value: String
},
model: {
prop: 'value',
event: 'change'
}
})
正如你所看到的,v-model 在 v-bind 的基础上是一种语法糖,它将一个值绑定到标记上,并在用户输入事件上更新数据,这些事件可以是 change 或 input 事件。简而言之,v-model 在幕后结合了 v-bind 和 v-on,但重要的是要理解语法下面的内容,作为 Vue/Nuxt 应用程序开发者。
你可以在我们的 GitHub 存储库的/chapter-7/vue/html/目录中找到本节中涵盖的示例。
现在你已经了解了 v-model 指令在表单输入元素中的工作方式,让我们在下一节中在表单上使用这些 v-model 元素并对其进行验证。
使用基本数据绑定验证表单
表单是收集信息的文件。HTML
稍后我们将在