五、制作自己的指令和插件

在本章中,我们将介绍扩展 Vue 的方法。首先,我们将编写自己的指令,并看看如何使用它们。接下来,我们将制作一个自定义 Vue 插件。

更具体地说,在本章中,我们将研究以下内容:

  • 自定义指令的结构及其生成方法
  • 使用全局和本地自定义指令
  • 将值传递给自定义指令
  • 创作视图插件
  • 发布视图插件

制定我们自己的指令

在 Vue 2 中,组件是要使用的策略,无论是保持干爽还是抽象一些功能。但是,您可以采取的另一种方法是利用自定义指令。

理解自定义指令

正如我们在本书前面所讨论的,指令帮助我们向 Vue 解释我们希望将什么样的行为附加到标记上。如前所述,Vue 内置了许多指令。例如v-onv-ifv-model等等。作为快速复习,指令是以[T3]开头的 HTML 属性。

当我们需要构建自定义指令时,我们只需在连字符后面提供一个自定义单词。例如,我们可以创建一个自定义指令,我们称之为v-custom-directive,然后我们可以在标记中使用这个名称,例如:

<div id="app">
  <div v-custom-directive>{{ something }}</div>
<!-- etc -->

请注意,没有值的指令是完全正常的,就像为其提供值一样,如下所示:

<div id="app">
  <div v-custom-directive="aValue">{{ something }}</div>
<!-- etc -->

接下来,在我们的 JS 代码中,我们需要注册此指令,如下所示:

Vue.directive('customDirective', {
  // details for the directive go here
}

因此,正如我们所看到的,Vue.directive提供的第一个参数是我们的定制指令的名称。请注意,在 HTML 中使用 kebab case 和在 JS 中使用 lowerCamelCase 的 Vue 约定也适用于自定义指令。

提供给自定义指令的第二个参数是一个包含该指令所有功能的对象。

正如您现在可能推断的那样,前面的代码给出了全局注册指令的示例。如果要在本地注册指令,则需要为特定组件指定一个directives选项。

例如,我们可以按如下方式注册本地组件:

directives: {
  directiveName: {
    // some code to describe functionality
  }
}

就像组件一样,指令也使用挂钩,这允许我们控制何时调用它们的功能。有五个指令钩子:bindinsertedupdatecomponentUpdatedunbind

对于这些钩子中的一些可以接受的参数的完整列表,您可以参考https://vuejs.org/v2/guide/custom-directive.html#Directive-钩子参数

构建简单的自定义指令

此示例的完整代码可在此处获得:https://codepen.io/AjdinImsirovic/pen/yxWObV

在 HTML 中,我们将添加以下简单代码:

<div id="app" class="container mt-5">
  <h1 class="h2">{{ heading }}</h1>
  <div v-custom-directive>
    Just some text here
  </div>
</div>

在我们的 JS 中,我们将在全球范围内添加我们的customDirective

Vue.directive('customDirective', {
  inserted: function(el) {
    el.style.cssText = `
      color: blue; 
      border: 1px solid black; 
      background: gray; 
      padding: 20px; 
      width: 50%;
    `
  }
});

new Vue({
  el: '#app',
  data() {
    return {
      heading: 'A custom global directive'
    }
  }
});

在前面的代码中,我们使用的是inserted指令钩子。有了这个钩子,当指令绑定到的元素插入其父节点时,指令的代码将运行。

发生这种情况时,将根据我们分配给el.style.cssText的值对元素进行样式设置。

当然,没有什么可以阻止我们在一个元素上使用多个自定义指令。例如,我们可以指定几个自定义指令,然后根据需要混合和匹配它们。

在下一节中,我们将把全局自定义指令重写为本地指令。

使用本地指令

现在让我们看看如何重写前面的代码,以便我们的指令使用本地指令而不是全局指令

在本节中,我们将构建一个非常简单的自定义指令。我们将使用第 4 章过滤器和混合器中的一个示例,我们将在此基础上进行构建,以便我们可以轻松地将差异与前面的示例进行比较,只是这次使用了一个简单的本地自定义指令。

此示例的代码在此处可用:https://codepen.io/AjdinImsirovic/pen/yxWJNp

在 HTML 中,我们将指定以下代码:

<main id="app">
    <custom-article v-custom-directive></custom-article>
    <custom-article></custom-article>
    <another-custom-article v-another-custom></another-custom-article>
</main>

在我们的 JS 中,我们将指定以下代码:

const anotherCustom = {
  inserted: function(el) {
    el.style.cssText = `
      color: green; 
      border: 1px solid black; 
      background: yellow; 
      padding: 20px; 
      width: 50%;
    ` 
  }
}

const customArticle = {
  template: `
    <article>
      Our own custom article component!
    </article>`
}

Vue.component('another-custom-article', {
  template: `
    <article>
      Another custom article component! 
      This one has it's own child component too!
      Here it is:
      <custom-article v-custom-directive></custom-article>
    </article>`,
    components: {
      'customArticle': customArticle
    },
    directives: {
      customDirective: {
        inserted: function(el) {
          el.style.cssText = `
            color: blue; 
            border: 1px solid black; 
            background: gray; 
            padding: 20px; 
            width: 50%;
          ` 
      }
    } 
  } 
})

new Vue({
    el: '#app',
    components: { 
      'customArticle': customArticle,
    },
    directives: {
      customDirective: {
        inserted: function(el) {
          el.style.cssText = `
            color: blue; 
            border: 1px solid black; 
            background: gray; 
            padding: 20px; 
            width: 50%;
          ` 
       }
     },
     'anotherCustom': anotherCustom
  }
})

在下一节中,我们将看到如何将值传递给自定义指令。

将值传递给自定义指令

我们将通过允许自定义指令接收参数来改进本章的初始示例。此示例的代码在此笔中可用:https://codepen.io/AjdinImsirovic/pen/xaNgPN

这是我们向自定义指令传递值示例的 HTML:

<div id="app" class="container mt-5">
  <h1 class="h2">{{ heading }}</h1>
  <button v-buttonize="tomato">
    Just some text here
  </button>
  <button v-buttonize="lightgoldenrod">
    Just some text here
  </button>
  <button v-buttonize="potato">
    Just some text here
  </button> 
</div>

下面是 JavaScript:

Vue.directive('buttonize', {
  bind(el, binding) {
    var exp = binding.expression;
    el.style.cssText += `
      padding: 10px 20px; 
      border: none; 
      border-radius: 3px; 
      cursor: pointer
    `;
    switch(exp) {
      case 'tomato':
          el.style.cssText += `
            background: tomato;
            color: white;
          `;
          break;
       case 'lightgoldenrod':
          el.style.cssText += `
            background: darkgoldenrod;
            color: lightgoldenrod;
          `;
          break;
        default:
            el.style.cssText += `
              background: gray;
              color: white;
            `
    }
  }
});

最后,还是在 JS 中,我们添加了带有options对象的 Vue 构造函数:

new Vue({
  el: '#app',
  data() {
    return {
      heading: 'A custom global directive'
    }
  }
});

注意,指令钩子参数的具体设置可以在中找到 https://vuejs.org/v2/guide/custom-directive.html#Directive-钩子参数。我们最感兴趣的一个论点是binding,它是一个具有以下属性的对象:namevalueoldValueexpressionargmodifiers

在前面的代码中,我们看到了一个传递两个不同值的示例,这两个值根据传递的值给出不同的结果。我们还看到了一个例子,当我们传递一个无意义的值(一个利用[T0]语句的[T1]分支的值)时会发生什么。

在下一节中,我们将讨论通过构建 Vue 插件进一步扩展 Vue 功能的方法。

使用 Vue 插件

一些流行的 Vue 插件是 Vuex 和 Vue 路由。当我们需要在全球范围内为 Vue 提供附加功能时,可以使用 Vue 插件。有几个非常常见的场景,Vue 插件可能很有用:添加全局方法、添加全局资产、在Vue.prototype上添加实例方法或添加全局混合

Vue 插件的亮点在于能够与社区共享它们。要了解 Vue 插件系统的广泛性,请浏览以下 URL:[T0]https://github.com/vuejs/awesome-vue#components--图书馆和https://vuejsexamples.com/

接下来,我们将创建一个简单的 Vue 插件。

创建最简单的 Vue 插件

我们将从创建最简单的 Vue 插件开始。为此,我们将再次使用 Vue CLI 版本 3。有关设置 Vue CLI 的说明,请参见第 3 章使用 Vue CLI、组件、属性和插槽

首先,我们需要初始化一个新项目。将控制台导航到要在其中创建新 Vue 项目的父文件夹,然后运行以下命令:

vue create simple-plugin
cd simple-plugin
npm run-serve

当我们第一次运行这三个命令包时,我们会被问到很多问题。这可能需要一些时间——一个短暂休息的好机会。一旦完成,并且我们已经运行了前面列出的另外两个命令,我们的样板 Vue 应用程序将在localhost:8080上可用

首先,让我们在src文件夹中创建一个新文件夹,并将其命名为plugins。接下来,在plugins文件夹中,让我们制作另一个文件夹,我们称之为SimplePlugin。在SimplePlugin文件夹中,让我们创建一个新文件并将其命名为index.js

Vue 插件是一个对象。为了使我们的 Vue 应用程序能够访问我们的插件对象,我们需要通过导出它使其可用。因此,让我们将此导出代码添加到我们的index.js文件中:

export default {

}

Vue 的插件对象有一个install方法。install方法包含两个参数。第一个参数是Vue对象,第二个参数是options对象。因此,我们将在plugin对象中添加install方法:

export default {
    install(Vue, options) {
        alert('This is a simple plugin and currently the options argument is ' + options);
    }
}

目前,在我们的install方法中,我们只向浏览器发出消息警报。这是我们的插件所能拥有的功能的绝对最小值。有了这个功能,是时候在我们的应用程序中使用我们的插件了。

Note that we are also concatenating the options argument to our alert message. If we didn't do it, our Vue-cli would throw an error, stating that options is defined but never used. Apparently, it favors the (no-unused-vars) scenarios.

要使用插件,我们需要打开我们的main.js文件,通过在main.js文件的第三行添加这两行代码来导入插件:

import SimplePlugin from './plugins/SimplePlugin'
Vue.use(SimplePlugin)

首先,我们导入插件并指定导入路径。接下来,我们将插件作为参数添加到Vue.use方法中。

有了这个,我们成功地编写了最简单的插件。在localhost:8080打开您的本地项目,您将收到警告消息,说明:

This is the simplest possible Vue plugin and currently the options argument is undefined

接下来,我们将看到如何将 options 对象添加到插件中。

创建定义了选项的插件

由于我们设置项目的方式,我们将保持SimplePlugin不变,在探索 Vue 插件的这一部分中,我们将在项目的plugins文件夹中添加另一个文件夹。我们将此文件夹命名为OptionsPlugin,在其中,我们将再次创建一个index.js文件。

接下来,让我们更新main.js文件,使其看起来像这样:

import Vue from 'vue'
import App from './App.vue'

//import SimplePlugin from './plugins/SimplePlugin'
import OptionsPlugin from './plugins/OptionsPlugin'

//Vue.use(SimplestPlugin)
Vue.use(OptionsPlugin)

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')

现在,回到OptionsPlugin/index.js中,我们将添加以下代码:

export default {
  install(Vue) {
    Vue.directive('text-length', {
        bind(el, binding, vnode) {
            const textLength = el.innerText.length;
            console.log("This element, " + el.nodeName + ", has text with " + textLength + " characters");

            el.style.cssText = "border: 2px solid tomato";
        }
    })
  }
}

Notice that we have completely omitted the options object in the install method. The reason is simple: the options object is optional, and not providing it will not break our code.

在前面的插件定义中,我们得到了el.innerText字符串的长度,然后我们将其注销到控制台。此外,应用了我们插件的自定义v-text-length指令的el也将以红色边框变得更加明显。

接下来,让我们在组件模板中使用插件的功能。具体来说,我们将在src/components文件夹中HelloWorld.vue文件的开头使用它:

<template>
  <div class="hello">
    <h1 v-text-length>{{ msg }}</h1>

此时在浏览器中运行我们的应用程序将在控制台中生成以下消息:

This element, H1, has text with 26 characters

现在,我们可以介绍我们的options对象。options对象的目的是允许我们自定义受v-text-length指令影响的 HTML 元素的显示方式。换句话说,我们可以决定让插件的用户根据传入的选项在不同类型的样式之间进行选择。

因此,让我们用以下代码更新插件:

const OptionsPlugin = { 
  install(Vue, options) {
    Vue.directive('text-length', {
        bind(el, binding, vnode) {
            const textLength = el.innerText.length;
            console.log("This element, " + el.nodeName + ", has text with " + textLength + " characters");

      if (textLength < 40) {
        el.style.cssText += "border:" + options.selectedOption.plum;
      } else if (textLength >= 40) {
        el.style.cssText += "border:" + options.selectedOption.orange;
      }
        }
    })
  }
};

export default OptionsPlugin;

在前面的代码中发生了一些事情。首先,我们正在动态创建一个对象,并将其分配给const OptionsPlugin。在文件的底部,我们正在导出刚刚定义的OptionsPlugin

optionsPlugin对象内部,我们使用两个 if 语句根据el元素的文本节点中的文本长度提供不同的样式。如果文本长度小于 40 个字符,那么我们将为[T3]CSS 属性指定值[T2]

否则,如果文本长度等于或大于 40 个字符,我们将把[T0]的值分配给所讨论元素的内联[T2]属性内的[T1]CSS 属性。

接下来,让我们设置这些选项值。我们将在我们的main.js文件中这样做。我们将使用插件的部分更新为以下代码:

Vue.use(OptionsPlugin, {
  selectedOption: {
    plum: "5px dashed purple",
    orange: "10px double orange"
  }
})

最后,在HelloWorld.vue文件中,我们只做了一个轻微的更新。我们将插件定义的指令添加到紧跟在h1标记之后的p标记中:

<template>
  <div class="hello">
    <h1 v-text-length>{{ msg }}</h1>
    <p v-text-length>

现在,当我们运行应用程序时,将在控制台上记录以下文本:

This element, H1, has text with 26 characters
This element, P, has text with 121 characters

在我们的视口中,该插件将在[T0]标记周围添加一个紫色虚线边框,并在[T1]标记周围添加一个橙色双边框。

现在我们已经了解了创建和使用插件的基本方式,我们可以想出一些创造性的方法来让我们的插件做一些更有用的事情。例如,我们可以通过添加一个工具提示来改进现有插件,该提示将显示页面上不同元素中出现的单词数。我们还可以添加颜色强度:单词越多,我们可以为“字符计数”徽章添加的颜色就越多。

或者,我们可以列出 style 属性或 class 属性中的值,或者两者都列出。这个插件将有助于在不打开开发工具的情况下快速检查样式,这可能在较小的屏幕或只有一个屏幕可用的工作站上证明是有用的。

接下来,我们将讨论如何发布 Vue 插件。具体来说,我们将发布我们刚刚制作的 OptionsPlugin。

发布视图插件

编写npm插件的先决条件是在网站上注册并验证您的电子邮件地址。因此,在npm上编写 Vue 插件的第一步是访问https://www.npmjs.com 并注册一个账户。

我们将在npm上发布我们的 Vue 插件。首先,让我们检查是否已经有用户。在控制台中运行以下命令:

npm whoami

如果出现错误,则需要通过运行以下命令创建新用户:

npm adduser

然后,只需按照说明添加您自己作为用户。

添加一个简单的插件

要添加一个简单的单文件插件,只需在您选择的文件夹中运行npm init。此命令将帮助您创建一个package.json文件

以下是提供的问题和答案列表:

package name: "vue-options-plugin"
version: (1.0.0)
description: A simple Vue plugin that shows how to use the options object
entry point: "OptionsPlugin.vue"
test command:
git repository:
keywords:
license: (ISC)
About to write to ...
Is this ok? (yes)

npm init实用程序提供的默认答案列在圆括号中。要接受默认设置,只需按回车键。否则,只需键入所需的答案。

还有npm作者的范围概念。作用域就是你的用户名。不必担心作用域的最佳方法是通过命令行在.npmrc文件中设置作用域,方法是运行以下命令:

npm init --scope=username

当然,您需要用您的实际用户名替换单词username

完成后,运行dir命令列出文件夹的内容。它应该只列出一个文件:package.json。现在,我们可以创建另一个名为OptionsPlugin.vue的文件:

touch OptionsPlugin.vue

让我们快速确认我们的package.json文件如下所示:

{
  "name": "vue-options-plugin",
  "version": "1.0.0",
  "description": "A simple Vue plugin that shows how to use options object",
  "main": "OptionsPlugin.vue",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "<your-username-here>",
  "license": "ISC"
}

接下来,我们用以下代码更新OptionsPlugin.vue文件:

const OptionsPlugin = { 
  install(Vue, options) {
    Vue.directive('text-length', {
        bind(el, binding, vnode) {
            const textLength = el.innerText.length;
            console.log("This element, " + el.nodeName + ", has text with " + textLength + " characters");

      if (textLength < 40) {
        el.style.cssText += "border:" + options.selectedOption.plum;
      } else if (textLength >= 40) {
        el.style.cssText += "border:" + options.selectedOption.orange;
      }
        }
    })
  }
};

export default OptionsPlugin;

最后,让我们添加一个README.md文件。md文件扩展名代表 Markdown,这是一种非常容易在线创作内容的格式。我们将在README中增加以下内容:

# optionsplugin
<p> A demo of making a simple Vue 2 plugin and using it with values stored in the options object. This plugin logs out to the console the number of characters in an element. It also adds different CSS styles based on the length of characters in the element.</p>

## Installation
```bash
 npm install --save optionsplugin
```js
## Configuration
```javascript
import Vue from 'vue';
import OptionsPlugin from 'optionsplugin'
Vue.use(OptionsPlugin, {
  selectedOption: {
    plum: "5px dashed purple",
    orange: "10px double orange"
  }
})
```js
## Usage
<p>To use it, simply add the plugin's custom directive of v-text-length to an element in your template's code.</p>

这应该是我们插件描述的一个很好的起点。我们以后可以随时改进[T0]。现在我们已经准备好了package.jsonREADME.mdOptionsPlugin.vue,我们只需运行以下命令即可发布我们的插件:

npm publish --access=public

我们需要为我们的npm publish命令提供--access=public标志,因为作用域包默认为私有访问,我们需要显式覆盖此设置。

发布后,我们的控制台将注销以下信息:

+ vue-options-plugin@1.0.0

这是我们成功发布插件的标志。我们的新插件现在有了自己的主页,网址如下:

https://www.npmjs.com/package/vue-options-plugin

最后,让我们看看如何将新添加的插件安装到另一个项目中

使用 Vue CLI 3 在 Vue 项目中安装我们的 NPM 插件

要从npm安装我们的 Vue 插件,我们需要首先创建一个新项目。让我们运行以下命令:

vue create just-another-project
cd just-another-project
npm run-serve

现在,我们可以通过运行以下命令来添加我们的npm插件:

npm install --save vue-options-plugin

这就是它的全部;现在,我们的插件可以在我们的项目中使用,我们可以像前面所描述的那样通过如下方式导入它:

import VueOptionsPlugin from 'vue-options-plugin'

现在,我们可以根据需要使用插件的功能。

要学习的其他插件

查看他人代码的良好编码示例总是很好的,这样我们就可以从中学习。一些有用的插件,我们可以从中学习,并可能有助于这些:

总结

在本章中,我们将介绍如何在 Vue 中创建自定义指令和自定义插件。我们介绍了如何构造自定义指令,以及如何生成全局和本地自定义指令。我们还研究了如何将值传递给自定义指令以及如何使用 Vue 插件。我们研究了如何创建两个自定义 Vue 插件。最后,我们已经了解了如何将我们的插件发布到npm,以及如何在 NPM 的项目中安装它。

在接下来的章节中,我们将研究如何通过过渡和动画使我们的应用程序更具交互性。