NuxtJS-Web-开发实用指南-三-

48 阅读33分钟

NuxtJS Web 开发实用指南(三)

原文:zh.annas-archive.org/md5/95454EEF6B1A13DFE0FAD028BE716A19

译者:飞龙

协议:CC BY-NC-SA 4.0

编写插件和模块

还记得自从第三章以来在 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-routervue-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(<plugin>, <options>)

例如,我们可以将以下选项传递到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参数配置语言;当没有提供选项时,将使用英语作为默认语言:

  1. /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的方式。此外,我们应该使用$作为方法和属性的前缀,因为这是一种惯例。

  1. 安装和配置此插件如下:
// src/entry.js
import PluginSample from './plugins/basic'
Vue.use(PluginBasic, {
  language: 'ES'
})
  1. 然后我们可以在任何 Vue 组件中全局使用该插件,就像以下示例中一样:
// src/components/home.vue
<p>{{ $greet('John') }}</p>
<p>{{ $message }}</p>
<p>{{ messages }}</p>

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!" ]

您还可以在插件中使用componentdirective,就像以下示例中一样:

// 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/目录中,然后执行以下步骤来安装它:

  1. 创建一个basic-import.js文件,以以下方式在/plugins/目录中导入basic.js
// plugins/basic-import.js
import Vue from 'vue'
import PluginSample from './basic'

Vue.use(PluginSample)

这次在使用Vue.use方法安装插件时,我们跳过了选项。

  1. basic-import.js的文件路径添加到 Nuxt 配置文件的plugins选项中,如下所示:
export default {
  plugins: [
    '~/plugins/basic-import',
  ]
}
  1. 在任何喜欢的页面中使用此插件-就像我们在 Vue 应用程序中所做的那样:
// pages/index.vue
<p>{{ $greet('Jane') }}</p>
<p>{{ $message }}</p>
<p>{{ messages }}</p>

export default {
  data () {
    let helloWorld = []
    helloWorld.push(this.$message)

    this.$message = 'Olá Mundo!'
    helloWorld.push(this.$message)

    return { messages: helloWorld }
  }
}
  1. 在浏览器上运行 Nuxt 应用程序,您应该在屏幕上看到以下输出:
Hello! Jane
Olá Mundo!
[ "Helló Világ!", "Olá Mundo!" ]

这次我们使用$greet方法得到了英文版的“Hello!”,因为在安装插件时没有设置任何语言选项。此外,在这个索引页面的<template>块中,你将得到“Olá Mundo!”的$message,而在其他页面(例如/about/contact)上,你将得到“Helló Világ!”,因为我们只在索引页面上设置了这个葡萄牙语版本的“Hello World!”,即this.$message = 'Olá Mundo!'

正如我们在本章开头提到的,有一个庞大的社区贡献的 Vue 插件集合,可能对你的 Nuxt 应用程序有用,但是一些插件可能只在浏览器中工作,因为它们缺乏 SSR(服务器端渲染)支持。因此,在接下来的部分,我们将看看如何解决这种类型的插件。

导入没有 SSR 支持的外部 Vue 插件

在 Nuxt 中,有一些 Vue 插件已经预先安装好了,比如vue-routervue-metavuexvue-server-renderer。未安装的插件可以按照我们在上一节中安装自定义 Vue 插件的步骤轻松排序。以下是我们如何在 Nuxt 应用程序中使用vue-notifications的示例:

  1. 使用 npm 安装插件:
$ npm i vue-notification
  1. 导入并注入插件,就像我们使用自定义插件一样:
// plugins/vue-notifications.js
import Vue from 'vue'
import VueNotifications from 'vue-notifications'

Vue.use(VueNotifications)
  1. 将文件路径包含到 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/<function-name>.js
import Vue from 'vue'
Vue.prototype.$<function-name> = () => {
  //...
}
  • Nuxt 上下文(服务器端):
// plugins/<function-name>.js
export default (context, inject) => {
  context.app.$<function-name> = () => {
    //...
  }
}
  • Vue 实例和 Nuxt 上下文:
// plugins/<function-name>.js
export default (context, inject) => {
  inject('<function-name>', () => {
    //...
  })
}

使用上述格式,你可以轻松地为你的应用编写全局函数。在接下来的章节中,我们将指导你通过一些示例函数。所以让我们开始吧。

将函数注入到 Vue 实例中

在这个例子中,我们将创建一个用于计算两个数字之和的函数,例如,1 + 2 = 3。我们将通过以下步骤将这个函数注入到 Vue 实例中:

  1. 创建一个.js文件,导入vue,并将函数附加到vue.prototype中的/plugins/目录中:
// plugins/vue-injections/sum.js
import Vue from 'vue'
Vue.prototype.$sum = (x, y) => x + y
  1. 将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js
export default {
  plugins: ['~/plugins/vue-injections/sum']
}
  1. 在任何你喜欢的地方使用这个函数,例如:
// pages/vue-injections.vue
<p>{{ this.$sum(1, 2) }}</p>
<p>{{ sum }}</p>

export default {
  data () {
    return {
      sum: this.$sum(2, 3)
    }
  }
}
  1. 在浏览器上运行页面,你应该在屏幕上得到以下输出(即使刷新页面):
3
5

将函数注入到 Nuxt 上下文中

在这个例子中,我们将创建一个用于计算一个数字的平方的函数,例如,5 * 5 = 25。我们将通过以下步骤将这个函数注入到 Nuxt 上下文中:

  1. 创建一个.js文件,并将函数附加到context.app中:
// plugins/ctx-injections/square.js
export default ({ app }, inject) => {
  app.$square = (x) => x * x
}
  1. 将函数文件路径添加到 Nuxt 配置文件的plugins选项中:
// nuxt.config.js
export default {
  plugins: ['~/plugins/ctx-injections/square']
}
  1. 在任何你喜欢的页面上使用这个函数,只要你可以访问到上下文,例如在asyncData方法中:
// pages/ctx-injections.vue
<p>{{ square }}</p>

export default {
  asyncData (context) {
    return {
      square: context.app.$square(5)
    }
  }
}
  1. 在浏览器上运行页面,你应该在屏幕上得到以下输出(即使刷新页面):
25

请注意,asyncData总是在页面组件初始化之前调用,你不能在这个方法中访问this。因此,你不能在asyncData方法中使用你注入到 Vue 实例($root)中的函数,比如我们在前面例子中创建的$sum函数(我们将在第八章中更详细地了解asyncData)。同样,我们也不能在 Vue 的生命周期钩子/方法(例如mountedupdated等)中调用上下文注入的函数,比如这个例子中的$square函数。但是,如果你想要一个可以从thiscontext中使用的函数,让我们看看如何通过在下一节中将这种函数注入到 Vue 实例和 Nuxt 上下文中来实现。

将函数注入到 Vue 实例和 Nuxt 上下文中

在这个例子中,我们将创建一个用于计算两个数字之积的函数,例如,2 * 3 = 6。我们将通过以下步骤将这个函数注入到 Vue 实例和 Nuxt 上下文中:

  1. 创建一个.js文件,并使用inject函数封装您的函数:
// plugins/combined-injections/multiply.js
export default ({ app }, inject) => {
  inject('multiply', (x, y) => x  y)
}

请注意,$会自动添加到您的函数前缀,因此您不必担心将其添加到您的函数中。

  1. 将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js
export default {
  plugins: ['~/plugins/combined-injections/multiply']
}
  1. 在任何您可以访问contextthis(Vue 实例)的页面上使用该函数,例如以下示例:
// pages/combined-injections.vue
<p>{{ this.$multiply(4, 3) }}</p>
<p>{{ multiply }}</p>

export default {
  asyncData (context) {
    return { multiply: context.app.$multiply(2, 3) }
  }
}
  1. 在浏览器上运行页面,您应该在屏幕上得到以下输出(即使在刷新页面时也是如此):
12
6

您可以在任何 Vue 生命周期钩子中使用该函数,例如以下示例:

mounted () {
  console.log(this.$multiply(5, 3))
}

您应该在浏览器控制台上得到15的输出。此外,您还可以从Vuex storeactionsmutations对象/属性中访问该函数,我们将在第十章中介绍添加一个 Vuex Store

  1. 创建一个.js文件,并将以下函数封装在actionsmutations对象中:
// 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)
  }
}
  1. 在任何您喜欢的页面上使用前面的存储action方法,例如以下示例:
// pages/combined-injections.vue
<p>{{ $store.state }}</p>
<button class="button" v-on:click="updateStore">Update Store</button>

export default {
  methods: {
    updateStore () {
      this.$store.dispatch('setNumbers')
    }
  }
}
  1. 在浏览器上运行页面,您应该在屏幕上得到以下输出(即使在刷新页面时也是如此):
{ "xNumber": 1, "yNumber": 3 }
  1. 单击“更新存储”按钮,前面的数字将更改为存储默认状态如下:
{ "xNumber": 24, "yNumber": 54 }

这很棒。通过这种方式,我们可以编写一个在客户端和服务器端都能工作的插件。但有时,我们需要能够在服务器端或客户端独占地使用的函数。为了做到这一点,我们必须指示 Nuxt 如何专门运行我们的函数。让我们在下一节中找出如何做到这一点。

注入仅客户端或仅服务器端的插件

在这个例子中,我们将创建一个用于除法的函数,例如,8 / 2 = 4,以及另一个用于减法的函数,例如,8 - 2 = 6。我们将将第一个函数注入到 Vue 实例中,并使其专门用于客户端使用,而将第二个函数注入到 Nuxt 上下文中,并使其专门用于服务器端使用。

  1. 创建两个函数,并将它们分别附加.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的函数文件将仅在服务器端运行。

  1. 将函数文件路径添加到 Nuxt 配置文件的plugins属性中:
// nuxt.config.js:
export default {
  plugins: [
    '~/plugins/name-conventions/divide.client.js',
    '~/plugins/name-conventions/subtract.server.js'
  ]
}
  1. 在任何你喜欢的页面上使用这些插件,比如以下示例:
// pages/name-conventions.vue
<p>{{ divide }}</p>
<p>{{ subtract }}</p>

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 }
  }
}
  1. 在浏览器上运行页面,你应该在屏幕上得到以下输出:
4
6

请注意,当你在浏览器上首次运行页面时,你将得到前面的结果,即使在刷新页面时也是如此。但是在第一次加载后,如果你通过<nuxt-link>导航到这个页面,你将在屏幕上得到以下输出:

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 上下文:nuxtServerInitasyncDatanuxtServerInit操作可以作为第二个参数访问上下文,而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/。现在,让我们看看如何在以下步骤中使用这个模块:

  1. 使用 npm 安装它:
$ npm install @nuxtjs/axios
  1. 在 Nuxt 配置文件中进行配置:
// nuxt.config.js
module.exports = {
  modules: [
    '@nuxtjs/axios'
  ]
}
  1. 在任何地方使用,例如在页面的asyncData方法中:
// pages/index.vue
async asyncData ({ $axios }) {
  const ip = await $axios.$get('http://icanhazip.com')
  console.log(ip)
}

您还可以在mounted方法(或createdupdated等)中使用它,如下所示:

// 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'
}
  • 配置选项:

我们还可以创建一个自定义选项(例如tokenproxybasic),并将一些特定选项传递给模块(这个自定义选项可以在模块之间共享使用),如下例所示:

// 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
}

然后我们可以将moduleOptionsthis.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 包,则此行是必需的,正如我们之前提到的。但在本书中,我们将指导您完成为您的项目创建模块的步骤。让我们通过以下步骤开始创建一个非常基本的模块:

  1. 创建一个带有以下代码的 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
  })
}
  1. 创建一个带有以下代码的 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

  1. 仅在 Nuxt 配置文件中包含模块文件路径(/modules/basic/module.js),并提供一些选项,如下所示使用 basic 自定义选项:
// nuxt.config.js
export default {
  modules: [
    ['~/modules/basic/module', { language: 'ES' }],
  ],

  basic: {
    option1: false,
    option2: true,
  }
}
  1. 您可以在任何地方使用它,例如:
// pages/index.vue
mounted () {
  const basic = this.$basic()
  console.log(basic)
}
  1. 每次访问主页时,您应该在浏览器控制台上看到以下输出:
["option 2", "language ES"]

请注意 module.js 如何处理高级配置细节,例如语言和选项。它还负责注册 plugin.js 文件,该文件执行实际工作。正如您所看到的,该模块是围绕插件的一个包装器。我们将在接下来的章节中更详细地学习这一点。

请注意,如果您只为构建时间和开发编写模块,则应在 Nuxt 配置文件中使用 buildModules 选项来注册您的模块,而不是在 Node.js 运行时使用 modules 选项。有关此选项的更多信息,请访问 nuxtjs.org/guide/modules#build-only-modulesnuxtjs.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的示例:

  1. 使用 npm 一起安装这两个模块:
$ npm i @nuxtjs/axios
$ npm i @nuxtjs/proxy

这两个模块被很好地集成在一起,以防止 CORS 问题,我们将在本书的后面看到并讨论跨域应用程序的开发。不需要手动注册@nuxtjs/proxy模块,但它确实需要在您的package.json文件的依赖项中。

  1. 在 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 的路由。

  1. 接下来,在任何组件中无缝使用它们,就像以下示例中一样:
// pages/index.vue
<template>
  <ul>
    <li v-for="user in users">
      {{ user.name }}
    </li>
  </ul>
</template>

<script>
export default {
  async asyncData({ $axios }) {
    const users = await $axios.$get('/api/users')
    return { users }
  }
}
</script>

现在,使用这两个模块,我们可以在请求方法(例如 getpost 和 put)中只写更短的 API 地址,比如 /api/users 而不是 https://jsonplaceholder.typicode.com/users。这样可以使我们的代码更整洁,因为我们不必在每次调用时都写完整的 URL。请注意,我们在 Nuxt 配置文件中配置的 /api/ 地址将被添加到对 API 端点的所有请求中。因此,我们使用 pathRewrite,如我们已经解释的那样,在发送请求时删除它。

你可以在以下链接中找到这两个模块提供的更多信息和顶层选项:

你可以在我们的 GitHub 仓库的 /chapter-6/nuxt-universal/module-snippets/top-level/ 中找到我们刚创建的示例模块片段。

使用 addPlugin 辅助函数

还记得我们在 编写基本模块 部分介绍过的 ModuleContainer 实例和 this.addPlugin 辅助方法吗?在这个示例中,我们将使用这个辅助函数创建一个模块,该模块通过这个辅助函数提供了一个插件,这个插件就是 bootstrap-vue,它将被注册到 Vue 实例中。让我们按照以下步骤创建这个模块片段:

  1. 安装 Bootstrap 和 BootstrapVue:
$ npm i bootstrap-vue
$ npm i bootstrap
  1. 创建一个插件文件来导入 vuebootstrap-vue,然后使用 use 方法注册 bootstrap-vue
// modules/bootstrap/plugin.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm'

Vue.use(BootstrapVue)
  1. 创建一个模块文件,使用 addPlugin 方法添加我们刚创建的插件文件:
// modules/bootstrap/module.js
import path from 'path'

export default function (moduleOptions) {
  this.addPlugin(path.resolve(__dirname, 'plugin.js'))
}
  1. 在 Nuxt 配置文件中添加这个 bootstrap 模块的文件路径:
// nuxt.config.js
export default {
  modules: [
    ['~/modules/bootstrap/module']
  ]
}
  1. 在任何喜欢的组件上开始使用 bootstrap-vue;例如,让我们创建一个按钮来切换 Bootstrap 中的警报文本,如下所示:
// pages/index.vue
<b-button @click="toggle">
  {{ show ? 'Hide' : 'Show' }} Alert
</b-button>
<b-alert v-model="show">
  Hello {{ name }}!
</b-alert>

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 的自定义<b-button>组件来切换 Bootstrap 的自定义<b-alert>组件。然后,<b-button>组件将在该按钮上切换文本“隐藏”或“显示”。

有关 BootstrapVue 的更多信息,请访问bootstrap-vue.js.org/。您可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/provide-plugin/中找到我们刚刚创建的示例模块片段。

使用 Lodash 模板

再次,这是我们在编写基本模块部分创建的自定义模块中熟悉的内容-利用 Lodash 模板通过使用 if 条件块来改变注册插件的输出。再次,Lodash 模板是一段代码,我们可以用<%= %>插值分隔符插入数据属性。让我们在以下步骤中尝试另一个简单的例子:

  1. 创建一个插件文件来导入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
  })
}
  1. 创建一个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
    }
  })
}
  1. 将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
  modules: [
      ['~/modules/users/module']
    ]
}
  1. 在任何您喜欢的组件上开始使用$getUsers方法,就像以下示例中一样:
// pages/index.vue
<li v-for="user in users">
  {{ user.name }}
</li>

export default {
  async asyncData({ app }) {
    const { data: users } = await app.$getUsers()
    return { users }
  }
}

在上面的示例中,Nuxt 将在将插件复制到项目时将options.url替换为https://jsonplaceholder.typicode.com/usersoptions.debug的 if 条件块将在生产构建时从插件代码中剥离,因此您在生产模式(npm run buildnpm run start)中将看不到终端上的console.log输出。

您可以在我们的 GitHub 存储库中的/chapter-6/nuxt-universal/module-snippets/template-plugin/中找到我们刚刚创建的示例模块片段。

添加 CSS 库

使用 addPlugin 助手部分的模块片段示例中,我们创建了一个模块,允许我们在应用程序中全局使用bootstrap-vue插件,而无需使用import语句来引入此插件,如下例所示:

// pages/index.vue
<b-button size="sm" @click="toggle">
  {{ show ? 'Hide' : 'Show' }} Alert
</b-button>

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
  //...
}

这看起来非常整洁,因为我们不必每次都导入bootstrap-vue,而只需导入 CSS 样式即可。但是,通过模块,我们仍然可以节省几行代码,将样式添加到应用程序的全局 CSS 堆栈中。让我们创建一个新的示例,并看看我们如何在以下步骤中执行该操作:

  1. 创建一个模块文件,其中包含一个名为optionsconst变量,用于将模块和顶层选项传递给插件文件,以及一个 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
  })
}
  1. 创建一个插件文件,其中注册了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) %>
<% } %>
  1. 将模块文件的文件路径添加到 Nuxt 配置文件中,模块选项指定是否在模块文件中禁用 CSS 文件。还要添加顶层选项bootstrap,以将布尔值传递给debug选项:
// nuxt.config.js
export default {
  modules: [
    ['~/modules/bootstrap/module', { styles: true }]
  ],

  bootstrap: {
    debug: process.env.NODE_ENV === 'development' ? true : false
  }
}
  1. 从我们的组件中删除 CSS 文件:
// pages/index.vue
<script>
- import 'bootstrap/dist/css/bootstrap.css'
- import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
  //...
}
</script>

因此,最终,我们可以在组件中使用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 图像上使用fillstroke和其他操作。我们还可以在 CSS、Sass、Less、Stylus 或 PostCSS 中使用它;例如,如果您想要用白色填充 SVG 图像,可以使用fillfff(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 图像:

  1. 使用 npm 安装加载器:
$ npm i svg-transform-loader
  1. 使用我们之前提供的模块/加载器模板创建一个模块文件,如下所示:
// modules/svg-transform-loader/module.js
export default function (moduleOptions) {
  this.extendBuild((config, { isClient, isServer }) => {
    //...
  })
}
  1. 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
  1. 在前面的代码块之后添加以下代码块,将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

  1. 将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
  modules: [
    ['~/modules/svg-transform-loader/module']
  ]
}
  1. 开始转换我们组件中的任何 SVG 图像,例如以下内容:
// pages/index.vue
<template>
  <div>
    <div class="background"></div>
    <img src="~/assets/bug.svg?stroke=red&stroke-
     width=4&fill=blue">
  </div>
</template>

<style lang="less">
.background {
   height: 100px;
   width: 100px;
   border: 4px solid red;
   background-image: url('~assets/bug.svg?stroke=red&stroke-
    width=2');
}
</style>

您可以在www.npmjs.com/package/svg-transform-loader找到有关svg-transform-loader的更多信息。如果您想了解有关规则测试的更多信息,并查看 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.<hookType>.<tap>('<PluginName>', (param) => {
        //...
      })
    }
  })
}

<tap>取决于挂钩类型;它可以是tapAsynctapPromisetap。让我们按照以下步骤通过 Nuxt 模块创建一个非常简单的“Hello World”webpack 插件:

  1. 使用我们提供的模块/插件架构创建一个模块文件,以打印“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(统计信息)被传递为参数。

  1. 将此模块的文件路径添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
 modules: [
 ['~/modules/hello-world/module']
}
  1. 使用$ npm run dev运行你的 Nuxt 应用程序,你应该在终端上看到“Hello World!”。

请注意,apply方法,compilerhookstap都是构建 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挂钩/事件:

  1. 创建一个模块文件,在所有模块加载完成时打印'All modules are loaded'
// modules/tasks/module.js
export default function (moduleOptions) {
  this.nuxt.hook('modules:done', moduleContainer => {
    console.log('All modules are loaded')
  })
}
  1. 创建几个模块来打印'Module 1''Module 2''Module 3'等,如下所示:
// modules/module1.js
export default function (moduleOptions) {
  console.log('Module 1')
}
  1. 将挂钩模块的文件路径和其他模块添加到 Nuxt 配置文件中:
// nuxt.config.js
export default {
  modules: [
    ['~/modules/tasks/module'],
    ['~/modules/module3'],
    ['~/modules/module1'],
    ['~/modules/module2']
  ]
}
  1. 使用$ npm run dev运行你的 Nuxt 应用程序,你应该在终端上看到以下输出:
Module 3
Module 1
Module 2
All modules are loaded

你可以看到挂钩模块总是最后打印,而其余的根据它们在modules选项中的顺序打印。

挂钩模块可以是异步的,无论你是使用async/await函数还是返回Promise

有关上述钩子和 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 元素(如texttextareacheckboxradioselect)中的工作原理。您将学会如何在 Vue 应用程序中验证这些元素,绑定默认和动态数据,并使用.lazy.trim等修饰符来修改或强制输入值。您还将学会使用 Vue 插件vee-validate对它们进行验证,然后将其应用到 Nuxt 应用程序中。我们将引导您顺利地完成所有这些领域。敬请关注。

添加 Vue 表单

在本章中,您将使用v-modelv-bind创建表单。您将学习在将表单数据发送到服务器之前在客户端验证表单。您将创建具有基本元素的表单,绑定动态值,并使用修饰符修改输入元素的行为。您还将学习如何使用vee-validate插件验证表单并将其应用于 Nuxt 应用程序。在本章中学习如何在 Vue 表单中使用v-modelv-bind非常重要,因为我们将在接下来的章节中使用表单,例如在“添加 Vuex 存储”第十章和“创建用户登录和 API 身份验证”第十二章中。

本章中我们将涵盖以下主题:

  • 理解v-model

  • 使用基本数据绑定验证表单

  • 创建动态值绑定

  • 使用vee-validate验证表单

  • 在 Nuxt 中应用 Vue 表单

第七章:理解v-model

v-model是 Vue 指令(自定义内置 Vue HTML 属性),允许我们在表单的inputtextareaselect元素上创建双向绑定。您可以将表单输入与 Vue 数据绑定,以便在用户与输入字段交互时更新数据。v-model始终会跳过您在表单元素上设置的初始值,而将 Vue 数据视为真相的来源。因此,您应该在 Vue 端,在data选项或函数内声明初始值。

v-model将根据输入类型选择适当的方式来更新元素,这意味着如果您在type="text"的表单输入上使用它,它将使用value作为属性,并使用input作为事件来执行双向绑定。让我们看看在接下来的部分中包括哪些内容。

在文本和文本区域元素中使用 v-model

记得我们在《添加 Vue 组件》的第五章中使用v-model实现双向绑定来创建自定义输入组件吗?在该章节的“创建自定义输入组件”部分,我们学到了输入框的v-model语法 - <input v-model="username"> - 实际上是以下内容的简写:

<input
  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: `<input v-on:input="$emit('input', $event.target.value)">`,
})

这仅仅是因为v-model输入的性质是由v-bind:valuev-on:input组成。当在textarea元素中使用v-model指令时,情况也是一样的,如下例所示:

<textarea v-model="message"></textarea>

这个v-model textarea元素是以下内容的简写:

<textarea
  v-bind:value="message"
  v-on:input="message = $event.target.value"
></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事件上更新的布尔值绑定,如下例所示:

<input type="checkbox" v-model="subscribe" value="yes" name="subscribe">

在上面的代码片段中,v-model checkbox输入元素确实是以下内容的简写:

<input
  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单选按钮输入元素,如下所示:

<input type="radio" v-model="answer" value="yes" name="answer">

前面的v-model元素是以下内容的另一种简写:

<input
  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-modelcheckboxradio 按钮输入元素总是绑定 value 属性,并在 change 事件上更新,因此自定义输入组件也必须采用相同的属性和事件。现在,让我们看看 v-model 在下一节中如何在 select 元素中工作。

在选择元素中使用 v-model

毫不奇怪,v-model select 输入元素总是将 value 属性与在 change 事件上获取其选定值的处理程序绑定,如下例所示:

<select
  v-model="favourite"
  name="favourite"
>
  //...
</select>

前面的 v-model checkbox 输入元素只是以下内容的另一种简写:

<select
  v-bind:value="favourite"
  v-on:change="favourite = $event.target.value"
  name="favourite"
>
  //...
</select>

因此,自定义的 checkbox 输入元素也必须始终遵守 v-model 元素的特性,使用 value 属性和 model 属性中的 change 事件,如下所示:

Vue.component('custom-select', {
  props: {
    value: String
  },
  model: {
    prop: 'value',
    event: 'change'
  }
})

正如你所看到的,v-modelv-bind 的基础上是一种语法糖,它将一个值绑定到标记上,并在用户输入事件上更新数据,这些事件可以是 changeinput 事件。简而言之,v-model 在幕后结合了 v-bindv-on,但重要的是要理解语法下面的内容,作为 Vue/Nuxt 应用程序开发者。

你可以在我们的 GitHub 存储库的/chapter-7/vue/html/目录中找到本节中涵盖的示例。

现在你已经了解了 v-model 指令在表单输入元素中的工作方式,让我们在下一节中在表单上使用这些 v-model 元素并对其进行验证。

使用基本数据绑定验证表单

表单是收集信息的文件。HTML <form> 元素是一个可以从网页用户那里收集数据或信息的表单。这个元素需要在其中使用 <input> 元素来指定我们想要收集的数据。但在接受数据之前,我们通常会希望对其进行验证和过滤,以便从用户那里获得真实和正确的数据。

Vue 允许我们轻松地从 v-model 输入元素中验证数据,因此让我们从单文件组件(SFC)Vue 应用程序和 webpack 开始,你可以在第五章中了解到,添加 Vue 组件,在使用 webpack 编译单文件组件部分。首先,我们将创建一个非常简单的表单,其中包括一个 submit 按钮和在 <template> 块中显示错误消息的标记,如下所示:

// src/components/basic.vue
<form v-on:submit.prevent="checkForm" action="/" method="post">
  <p v-if="errors.length">
    <b>Please correct the following error(s):</b>
    <ul>
      <li v-for="error in errors">{{ error }}</li>
    </ul>
  </p>
  <p>
    <input type="submit" value="Submit">
  </p>
</form>

稍后我们将在<form>中添加其余的输入元素。现在,让我们设置基本结构并了解我们将需要什么。我们使用v-on:submit.prevent来防止浏览器默认发送表单数据,因为我们将在 Vue 实例的<script>块中使用checkForm方法来处理提交:

// src/components/basic.vue
export default {
  data () {
    return {
      errors: [],
      form: {...}
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.errors.length) {
        this.processForm(e)
      }
    },
    processForm (e) {...}
  }
}

在 JavaScript 方面,我们定义一个数组来保存在验证过程中可能遇到的错误。checkForm逻辑验证我们稍后将在本节中添加的必填字段。如果必填字段未能通过验证,我们将错误消息推送到errors中。当表单填写正确和/或未发现错误时,它将被传递到processForm逻辑,在那里我们可以在将其发送到服务器之前对表单数据进行进一步处理。

验证文本元素

让我们开始添加一个用于单行文本的<input>元素:

// src/components/basic.vue
<label for="name">Name</label>
<input v-model="form.name" type="text">

export default {
  data () {
    return {
      form: { name: null }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.form.name) {
        this.errors.push('Name required')
      }
    }
  }
}

<script>块中,我们在data函数中定义了一个name属性,它保存初始的null值,并将在<input>元素的input事件上进行更新。当您点击submit按钮时,我们在if条件块中验证name数据;如果没有提供数据,那么我们将错误消息pusherrors中。

验证文本区域元素

我们要添加的下一个元素是<textarea>,用于多行文本,其工作方式与<input>相同:

// src/components/basic.vue
<label for="message">Message</label>
<textarea v-model="form.message"></textarea>

export default {
  data () {
    return {
      form: { message: null }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.form.message) {
        this.errors.push('Message required')
      }
    }
  }
}

<script>块中,我们在data函数中定义了一个message属性,它保存初始的null值,并将在<textarea>元素的input事件上进行更新。当您点击submit按钮时,我们在if条件块中验证message数据;如果没有提供数据,那么我们将错误消息pusherrors中。

验证复选框元素

下一个元素是一个单个复选框<input>元素,它将保存默认的布尔值:

// src/components/basic.vue
<label class="label">Subscribe</label>
<input type="checkbox" v-model="form.subscribe">

export default {
  data () {
    return {
      form: { subscribe: false }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.form.subscribe) {
        this.errors.push('Subscription required')
      }
    }
  }
}

我们还将添加以下多个复选框<input>元素,它们绑定到同一个数组books: []

// src/components/basic.vue
<input type="checkbox" v-model="form.books" value="On the Origin of Species">
<label for="On the Origin of Species">On the Origin of Species</label>

<input type="checkbox" v-model="form.books" value="A Brief History of Time">
<label for="A Brief History of Time">A Brief History of Time</label>

<input type="checkbox" v-model="form.books" value="The Selfish Gene">
<label for="The Selfish Gene">The Selfish Gene</label>

export default {
  data () {
    return {
      form: { books: [] }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (this.form.books.length === 0) {
        this.errors.push('Books required')
      }
    }
  }
}

<script>块中,我们在data函数中定义了一个subscribe属性,它保存初始的布尔值false,并将在复选框<input>元素的change事件上进行更新。当您点击submit按钮时,我们在if条件块中验证subscribe数据;如果没有提供数据或者为false,那么我们将错误消息pusherrors中。

我们通过定义一个books属性来实现多个复选框<input>元素的相同功能,它保存了初始的空数组,并将在复选框<input>元素的change事件上进行更新。我们在if条件块中验证books数据;如果长度为0,那么我们将错误消息pusherrors中。

验证单选按钮元素

接下来是绑定到相同属性名称的多个单选按钮<input>元素,即gender

// src/components/basic.vue
<label for="male">Male</label>
<input type="radio" v-model="form.gender" value="male">

<label for="female">Female</label>
<input type="radio" v-model="form.gender" value="female">

<label for="other">Other</label>
<input type="radio" v-model="form.gender" value="other">

export default {
  data () {
    return {
      form: { gender: null }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.form.gender) {
        this.errors.push('Gender required')
      }
    }
  }
}

<script>块中,我们在data函数中定义了一个gender属性,它保存了初始的null值,并将在选定的<input>单选按钮元素的change事件上进行更新。当点击submit按钮时,我们在if条件块中验证gender数据。如果没有提供数据,那么我们将错误消息pusherrors中。

验证选择元素

接下来是一个单个<select>元素,其中包含多个<option>元素,如下所示:

// src/components/basic.vue
<select v-model="form.favourite">
  <option disabled value="">Please select one</option>
  <option value="On the Origin of Species">On the Origin of 
   Species</option>
  <option value="A Brief History of Time">A Brief History of Time</option>
  <option value="The Selfish Gene">The Selfish Gene</option>
</select>

export default {
  data () {
    return {
      form: { favourite: null }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (!this.form.favourite) {
        this.errors.push('Favourite required')
      }
    }
  }
}

最后是多个绑定到相同Array的多个<option>元素的多个<select>元素,即favourites: []

// src/components/basic.vue
<select v-model="form.favourites" multiple >
  <option value="On the Origin of Species">On the Origin of 
   Species</option>
  <option value="A Brief History of Time">A Brief History of Time</option>
  <option value="The Selfish Gene">The Selfish Gene</option>
</select>

export default {
  data () {
    return {
      form: { favourites: [] }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (this.form.favourites.length === 0) {
        this.errors.push('Favourites required')
      }
    }
  }
}

<script>块中,我们在data函数中定义了一个favourites属性,它保存了初始的null值,并将在<select>元素的change事件上进行更新。当点击submit按钮时,我们在if条件块中验证favourites数据。如果没有提供数据,那么我们将错误消息pusherrors中。对于多个<select>元素,我们也是通过定义一个favourites属性来实现相同的功能,它保存了初始的空数组,并将在<select>元素的change事件上进行更新。我们在if条件块中验证favourites数据;如果长度为0,那么我们将错误消息pusherrors中。

然后我们将使用processForm逻辑完成这个表单,只有在checkForm逻辑中没有发现错误时才会调用。我们使用 Node.js 包qsthis.form对象进行字符串化,以便以以下格式将数据发送到服务器:

name=John&message=Hello%20World&subscribe=true&gender=other

让我们使用 npm 安装qs

$ npm i qs

然后我们可以按照以下方式使用它:

import axios from 'axios'
import qs from 'qs'

processForm (e) {
  var data = qs.stringify(this.form)
  axios.post('../server.php', data)
  .then((response) => {
    // success callback
  }, (response) => {
    // error callback
  })
}

我们使用axios发送数据,并从服务器获取响应(通常是 JSON 格式),然后您可以对响应数据进行操作,例如在服务器端显示“成功”或“失败”消息。

有关qs的更多信息,请访问www.npmjs.com/package/qs,有关axios,请访问github.com/axios/axios

您可以在我们的 GitHub 存储库的/chapter-7/vue/webpack/中找到前面的示例应用程序。

然而,我们还没有完全完成,因为有时我们可能希望将动态值绑定到表单输入中,而不是从v-model中获取默认值。例如,在我们的示例应用程序中,我们只使用单个复选框<input>元素获取subscribe属性的布尔值,但我们希望使用字符串值yesno。我们将在接下来的部分探讨如何更改默认值。

进行动态值绑定

在前一节的示例应用程序中,我们仅使用v-model获取radiocheckboxselect选项的字符串或布尔值。我们可以通过使用true-valuefalse-valuev-bind来更改此默认值。让我们深入了解。

用布尔值替换-复选框元素

我们可以通过使用true-valuefalse-value将我们的自定义值绑定到单个checkbox元素。例如,我们可以使用true-valueyes值绑定到替换默认的true布尔值,使用false-valueno值绑定到替换默认的false布尔值:

// src/components/dynamic-values.vue
<input
  type="checkbox"
  v-model="form.subscribe"
  true-value="yes"
  false-value="no"
>

export default {
  data () {
    return {
      form: { subscribe: 'no' }
    }
  },
  methods:{
    checkForm (e) {
      this.errors = []
      if (this.form.subscribe !== 'yes') {
        this.errors.push('Subscription required')
      }
    }
  }
}

现在,当您将subscribe输入的值发送到服务器时,您将得到yesno的响应。在<script>块中,我们现在将no声明为subscribe属性的初始值,并在if条件块中对其进行验证,以确保在点击submit按钮时它始终为yes,否则我们将错误消息推送到errors

用动态属性替换字符串-单选按钮元素

关于单选按钮<input>元素,我们可以使用v-bind将它们的值绑定到 Vue 实例中的动态属性:

// src/components/dynamic-values.vue
<input type="radio" v-model="form.gender" v-bind:value="gender.male">

export default {
  data () {
    return {
      gender: {
        male: 'm',
        female: 'f',
        other: 'o',
      },
      form: { gender: null }
    }
  }
}

现在,当选择此单选按钮时,您将得到m,验证逻辑与以前相同。

用对象替换字符串-选择选项元素

我们还可以使用v-bind非字符串值(如Object)绑定到表单输入。请参阅以下示例:

// src/components/dynamic-values.vue
<select v-model="form.favourite">
  <option v-bind:value="{ title: 'On the Origin of Species' }">On 
   the Origin of Species</option>
</select>

export default {
  data () {
    return {
      form: {
        favourite: null
      }
    }
  }
}

现在当选择此选项时,您将得到typeof this.favouriteobjectthis.favourite.titleOn the Origin of Species。验证逻辑没有变化。

我们还可以使用动态值和v-for动态呈现<option>元素:

// src/components/dynamic-values.vue
<select v-model="form.favourites" name="favourites_array[]" multiple >
  <option v-for="book in options.books" v-bind:value="book.value">
    {{ book.text }}
  </option>
</select>

data () {
  return {
    form: { favourites: [] },
    options: {
      books: [
        { value: { title: 'On the Origin of Species' }, text: 'On the 
         Origin of Species'},
        { value: { title: 'A Brief History of Time' }, text: 'A Brief 
         History of Time'},
        { value: { title: 'The Selfish Gene' }, text: 'The Selfish Gene'}
      ]
    }
  }
}

现在我们不再需要硬编码<option>元素了。我们可以从其他地方(如 API)获取books数据。

除了将动态值绑定到表单输入外,我们还可以修改输入元素上v-model的默认行为。例如,我们可以使用它们上的change事件,而不是将输入与数据同步。让我们在下一个主题中发现如何做到这一点。

使用修饰符

Vue 提供了这三个修饰符,.lazy.number.trim,我们可以与v-model一起使用,改变默认事件或为表单输入添加额外功能。让我们深入了解。

添加.lazy

我们可以使用.lazyv-model来将<input><textarea>元素上的input事件改为change事件。看下面的例子:

// src/components/modifiers.vue
<input v-model.lazy="form.name" type="text">

现在输入与数据在change之后同步,而不是默认的input事件。

添加.number

我们可以使用.numberv-model来改变<input>元素上type="number"的默认类型转换,将string转换为number。看下面的例子:

// src/components/modifiers.vue
<input v-model.number="form.age" type="number">

现在你得到typeof this.form.agenumber,而不是没有.numberstring

添加.trim

我们可以使用.trimv-model来修剪用户输入的空白。看下面的例子:

// src/components/modifiers.vue
<textarea v-model.lazy.trim="form.message"></textarea>

现在用户输入的文本会自动修剪。文本开头和结尾的任何额外空白都将被修剪掉。

虽然编写自定义验证逻辑是可能的,但已经有一个很棒的插件可以帮助轻松验证输入并显示相应的错误。这个插件叫做 VeeValidate,是一个基于 Vue 的模板验证框架。让我们在下一节中发现如何利用这个插件。

使用 VeeValidate 验证表单

使用 VeeValidate,我们将使用 VeeValidate 的组件来验证我们的 HTML 表单,并使用 Vue 的作用域插槽来暴露错误消息。例如,这是一个我们已经熟悉的v-model输入元素:

<input v-model="username" type="text" />

如果你想用 VeeValidate 验证它,你只需要用<ValidationProvider>组件包装输入:

<ValidationProvider name="message" rules="required" v-slot="{ errors }">
  <input v-model="username" name="username" type="text" />
  <span>{{ errors[0] }}</span>
</ValidationProvider>

通常情况下,我们使用<ValidationProvider>组件来验证<input>元素。我们可以使用rules属性将验证规则附加到这个组件上,并使用v-slot指令显示错误。让我们在以下步骤中发现如何利用这个插件来加快验证过程:

  1. 使用 npm 安装 VeeValidate:
$ npm i vee-validate
  1. /src/目录中创建一个.js文件,并使用 VeeValidate 的extend函数添加规则:
// src/vee-validate.js
import { extend } from 'vee-validate'
import { required } from 'vee-validate/dist/rules'

extend('required', {
  ...required,
  message: 'This field is required'
})

VeeValidate 提供了许多内置的验证规则,如requiredemailminregex等,因此我们可以导入我们应用程序所需的特定规则。因此,在上述代码中,我们导入required规则,并通过extend函数安装它,然后在message属性中添加我们的自定义消息。

  1. /src/vee-validate.js导入到初始化 Vue 实例的主入口文件中:
// src/main.js
import Vue from 'vue'
import './vee-validate'
  1. ValidationProvider组件本地导入到页面中,并开始验证该页面上的输入字段:
// src/components/vee-validation.vue
<ValidationProvider name="name" rules="required|min:3" v-slot="{ errors }">
  <input v-model.lazy="name" type="text" name="name">
  <span>{{ errors[0] }}</span>
</ValidationProvider>

import { ValidationProvider } from 'vee-validate'

export default {
  components: {
    ValidationProvider
  }
}

我们还可以在/src/main.js/src/plugins/vee-validate.js中全局注册ValidationProvider

import Vue from 'vue'
import { ValidationProvider, extend } from 'vee-validate'

Vue.component('ValidationProvider', ValidationProvider)

但是,如果您不需要在应用程序的每个页面上使用此组件,这可能不是一个好主意。因此,如果您只需要在某个页面上使用它,则将其本地导入。

  1. 本地导入ValidationObserver组件,并将passes对象添加到v-slot指令中。因此,让我们按照以下方式重构步骤 4中的 JavaScript 代码:
// src/components/vee-validation.vue
<ValidationObserver v-slot="{ passes }">
  <form v-on:submit.prevent="passes(processForm)" novalidate="true">
    //...
    <input type="submit" value="Submit">
  </form>
</ValidationObserver>

import {
  ValidationObserver,
  ValidationProvider
} from 'vee-validate'

export default {
  components: {
    ValidationObserver,
    ValidationProvider
  },
  methods:{
    processForm () {
      console.log('Posting to the server...')
    }
  }
}

我们使用<ValidationObserver>组件来包装<form>元素,以在提交之前告知其是否有效。我们还在<ValidationObserver>组件的作用域插槽对象中使用passes属性,该属性用于在表单无效时阻止提交。然后我们将我们的processForm方法传递给表单元素上的v-on:submit事件中的passes函数。如果表单无效,则不会调用我们的processForm方法。

就是这样。我们完成了。您可以看到,我们不再需要methods属性中v-on:submit事件上的checkForm方法,因为 VeeValidate 已经为我们验证了元素,并且现在我们的 JavaScript 代码变得更短了。我们只需要使用<ValidationProvider><ValidationObserver>组件包装我们的输入字段。

如果您想了解有关 Vue 插槽和 VeeValidate 的更多信息,请访问以下链接:

您可以在我们的 GitHub 存储库的/chapter-7/vue/cli/中找到我们先前的 Vue 应用程序的示例。

接下来,我们将在下一节中了解如何在 Nuxt 应用程序中应用 VeeValidate。

将自定义验证应用到 Nuxt 应用程序

让我们将自定义验证应用到我们已经有的示例网站中的联系页面。您可能已经注意到,现有的联系表单已经安装了 Foundation(Zurb)的验证。使用 Foundation 的表单验证是另一种提升我们的 HTML 表单验证的好方法。

如果您想了解更多关于 Foundation 的信息,可以在它们的官方指南中找到更多信息:foundation.zurb.com/sites/docs/abide.html

但是,如果我们想要在 Nuxt 中进行自定义验证,我们刚刚学习了在 Vue 应用程序中使用的 VeeValidate,那么让我们按照以下步骤安装和设置我们需要的内容:

  1. 通过 npm 安装 VeeValidate:
$ npm i vee-validate
  1. /plugins/目录中创建一个插件文件,并添加我们需要的规则,如下所示:
// plugins/vee-validate.js
import { extend } from 'vee-validate'
import {
  required,
  email
} from 'vee-validate/dist/rules'

extend('required', {
  ...required,
  message: 'This field is required'
})

extend('email', {
  ...email,
  message: 'This field must be a valid email'
})

这个文件中的一切都和我们在 Vue 应用程序中做的文件一样。

  1. 在 Nuxt 配置文件的plugins选项中包含插件路径:
// nuxt.config.js
plugins: [
  '~/plugins/vee-validate'
]
  1. 在 Nuxt 配置文件的build选项中为/vee-validate/dist/rules.js文件添加一个例外。
// nuxt.config.js
build: {
  transpile: [
    "vee-validate/dist/rules"
  ],
  extend(config, ctx) {}
}

在 Nuxt 中,默认情况下,/node_modules/文件夹被排除在转译之外,当使用vee-validate时,您将会收到一个错误消息Unexpected token export,因此在运行 Nuxt 应用程序之前,我们必须将/vee-validate/dist/rules.js添加到转译中。

  1. 像我们在 Vue 应用程序中所做的那样,导入ValidationObserverValidationProvider组件:
// pages/contact.vue
import {
  ValidationObserver,
  ValidationProvider
} from 'vee-validate'

export default {
  components: {
    ValidationObserver,
    ValidationProvider
  }
}
  1. <form>元素中删除 Foundation 的data-abide属性,但使用<ValidationObserver>组件将其包装起来,并将submit事件与passesprocessForm方法绑定到<form>元素,如下所示:
// pages/contact.vue
<ValidationObserver v-slot="{ passes }" ref="observer">
  <form v-on:submit.prevent="passes(processForm)" novalidate>
  //...
  </form>
</option>

这一步和我们在 Vue 应用程序中所做的步骤是一样的,但在这个例子中,我们在步骤 8中添加了ref="observer",因为我们将在后面需要它。

  1. 开始使用<ValidationProvider>组件重构<form>元素内的所有<input>元素,如下所示:
// pages/contact.vue
<ValidationProvider name="name" rules="required|min:3" v-slot="{ errors, invalid, validated }">
  <label v-bind:class="[invalid && validated ? {'is-invalid-label': 
   '{_field_}'} : '']">Name
    <input
      type="text"
      name="name"
      v-model.trim="name"
      v-bind:class="[invalid && validated ? {'is-invalid-input': 
       '{_field_}'} : '']"
    >
    <span class="form-error">{{ errors[0] }}</span>
  </label>
</ValidationProvider>

这一步和我们在 Vue 应用程序中所做的步骤是一样的,但在这个例子中,我们在v-slot指令中添加了两个作用域插槽数据属性,invalidvalidated,以便根据条件将类绑定到<label><input>元素。因此,如果invalidvalidated都为true,那么我们将分别将is-invalid-labelis-invalid-input类绑定到元素上。

有关验证提供程序的作用域插槽数据属性的更多信息,请访问vee-validate.logaretm.com/v2/guide/components/validation-provider.html#scoped-slot-data

  1. 通过向<script>块中的data函数添加以下数据属性来重构,以与v-model输入元素同步。我们还将在methods选项中添加两个方法,如下所示:
// pages/contact.vue
export default {
  data () {
    return {
      name: null,
      email: null,
      subject: null,
      message: null
    }
  },
  methods:{
    clear () {
      this.name = null
      this.email = null
      this.subject = null
      this.message = null
    },
    processForm (event) {
      alert('Processing!')
      console.log('Posting to the server...')
      this.clear()
      this.$refs.observer.reset()
    }
  }
}

这一步与我们在 Vue 应用中所做的相同,但在这个例子中,我们在methods选项中的processForm中添加了clear方法和reset方法。<ValidationObserver>组件在提交后不会重置表单的状态,因此我们必须手动进行,通过在步骤 6中将观察者作为引用传递,然后我们可以通过this.$refs从 Vue 实例中访问它。

  1. 将这三个作用域插槽数据属性dirtyinvalidvalidated添加到<ValidationObserver>组件中,以切换警报和成功消息,然后让我们按照以下方式重构这个组件:
// pages/contact.vue
<ValidationObserver v-slot="{ passes, dirty, invalid, validated }" ref="observer">
  <div class="alert callout" v-if="invalid && validated">
    <p><i class="fi-alert"></i> There are some errors in your 
     form.</p>
  </div>
  //...
  <div class="success callout" v-if="submitted && !dirty">
    <p><i class="fi-like"></i>&nbsp; Thank you for contacting
      me.</p>
  </div>
</ValidationObserver>

export default {
  data () {
    return {
      submitted: false
      //...
    }
  },
  methods:{
    processForm (event) {
      console.log('Posting to the server...')
      this.submitted = true
      //...
    }
  }
}

在最后一步中,我们添加了一个默认为falsesubmitted数据属性,当表单在processForm方法中提交时,它将被设置为true。另一方面,当作用域插槽中的invalidvalidated都为true时,警报消息块将可见,当submitted属性为truedirty作用域插槽数据属性为false时,成功消息块将可见。如果输入字段中有一个字母存在,我们将从dirty属性中得到一个true。换句话说,当输入字段中存在字母时。

您可以看到,在我们的 Nuxt 应用中重构的代码与我们在 Vue 标准应用中所做的非常相似。但是在 Nuxt 应用中,我们为表单添加了更复杂的逻辑,例如切换警报和成功消息,有条件地将类绑定到<label><input>元素,并在表单提交时重置<ValidationObserver>组件。对于其余的输入元素,重构过程是相同的,您可以在书的 GitHub 存储库/chapter-7/nuxt-universal/sample-website/中找到。

摘要

在本章中,我们已经介绍了使用v-model在各种表单输入上进行 Vue 表单验证。您已经学会了基本和动态值绑定,以及如何使用修饰符来更改默认的输入事件和类型转换。您还学会了使用vee-validate插件来简化验证过程。最后,我们成功将这些应用到了 Nuxt 应用程序中。

在下一章中,我们将探讨如何在 Nuxt 应用程序中添加服务器端框架。您将学会使用 Koa 创建一个简单的 API,并将其与 Nuxt 集成,使用 HTTP 客户端 Axios 来请求 API 数据。此外,您还将介绍一个基于 webpack 的极简构建系统,称为 Backpack,它将简化我们用于单文件组件 Vue 应用程序的自定义 webpack 配置。您还将学会如何在 Nuxt 应用程序中使用这个构建系统。敬请关注!