likes
comments
collection
share

Vue插件最佳实践:封装一个自己的表单验证插件

作者站长头像
站长
· 阅读数 55

插件通常用来为Vue添加全局功能,通过Vue.use(plugin)即可使用插件。

那么如何写一个插件呢?插件可以是一个函数,也可以是一个提供install方法的对象。这里以对象为例:

const myPlugin = {
  install(Vue){
    Vue.mixin({
      // ...
    })
  }
}

对象形式的插件必须提供 install 方法,install 方法调用时,会将 Vue 作为参数传入。

写插件涉及到Vue.mixinAPI,Vue.mixin是一个全局API,使用它可以向组件注入自定义行为。Vue.mixin接收一个option,这个option会应用到所有的Vue实例中,影响注册之后创建的每个Vue实例。mixin本质上是可以重复使用的Vue option代码片段。

引用官网上的例子:

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption;
    if (myOption) {
      console.log(myOption);
    }
  },
});

new Vue({
  myOption: "hello!",
});
// => "hello!"

这里的$options是实例属性,使用它可以访问到合并后的应用到Vue实例上的option。为什么说是合并后呢?因为Vue组件的option可以来自很多地方,可以是全局的mixin,可以是创建组件时传递给它的option,也可以是从另一个组件继承过来的option(使用Vue.extends时),这些option最终会合并成一个option,即每个实例的$option。在这个option中,可以找到添加到组件的自定义属性,即非内置的属性(内置指的是created,data,computed等属性),在上面的例子中就是myOption。

了解这些之后,就可以开始写表单验证插件了。

<div id="app">
  <form @submit="validate">
    <input v-model="text" />
    <br />
    <input v-model="email" />
    <ul v-if="!$v.valid" style="color: red">
      <li v-for="error in $v.errors">{{error}}</li>
    </ul>
    <input type="submit" :disabled="!$v.valid" />
  </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const emialRe = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  const validationPlugin = {
    install(Vue) {
      Vue.mixin({
        // ...插件的代码
      });
    },
  };
  Vue.use(validationPlugin);

  const app = new Vue({
    el: "#app",
    data: {
      text: "foo",
      email: "",
    },
    // 自定义的验证规则
    validations: {
      text: {
        validate: (value) => value.length >= 5,
        message: (key, value) =>
          `${key} should have a min length of length 5, bug got ${value.length}`,
      },
      email: {
        validate: (value) => emialRe.test(value),
        message: (key) => `${key} must be a valid email`,
      },
    },
    methods: {
      validate(e) {
        if (!this.$v.valid) {
          e.preventDefault();
          alert("not valid!");
        }
      },
    },
  });
</script>

先简单的介绍一下代码。我们在模板中放置了1个form表单,表单的submit事件绑定了validate方法,表单中包含了2个input输入框,一个双向绑定text,另一个双向绑定email。在表单下方,使用列表来展示验证失败的提示信息。如果表单验证不通过,显示错误提示,同时提交按钮变得不可用,如果表单验证通过,不显示错误提示,提交按钮可用。因为验证了多个字段,错误提示可能有多条,所以使用v-for来遍历展示所有的错误提示。

表单双向绑定了textemail,在validations中对要验证的属性声明对应的验证规则和提示文字,其中validate函数用于设定对应的验证规则,message函数用于返回验证失败时显示的提示文字。

我们要做的,就是编写validationPlugin中的代码,实现一个$v,使上面的表单验证代码能够正常运行。正常运行时应该如下图所示:

Vue插件最佳实践:封装一个自己的表单验证插件

通过观察,我们可以发现访问$v时,会获得一个包含valid和errors属性的对象。因此,可以在created生命周期钩子中设置watch,监听textemail的变化,对每个属性进行验证,然后将结果设置到$v中。这确实是一种可行的方法,但是实际上还有更好的方法。再观察一下$v,可以发现$v代表的是表单的验证状态,而我们并不会去手动修改这个状态,所以将$v设置成computed中的属性是一种更好的做法。代码如下:

 const validationPlugin = {
    install(Vue) {
      Vue.mixin({
        computed: {
          $v() {
            const rules = this.$options.validations;
            let valid = true;
            const errors = [];
            Object.keys(rules).forEach((key) => {
              const rule = rules[key];
              const value = this[key];
              const result = rule.validate(value);
              if (!result) {
                valid = false;
                errors.push(rule.message(key, value));
              }
            });
            return {
              valid,
              errors,
            };
          },
        },
      });
    },
  }

上面的代码虽然能够正常运行,但还有优化的空间。当应用了这个插件后,并不是所有的Vue实例都需要表单验证这个功能。更好的做法是只有在this.$options.validations存在时才在实例上添加$v属性,对于不存在this.$options.validations属性的Vue实例,不进行任何修改

对上面代码进行修改,在beforeCreate生命周期钩子中根据是否存在this.$options.validations来动态生成computed。之所以选择在beforeCreate而不是created生命周期钩子中修改computed,是因为在created中,computed已经被初始化,此时已经无法再修改computed了,而在beforeCreate中,响应系统还没有创建,因此可以修改computed。修改之后的代码如下:

  const validationPlugin = {
    install(Vue) {
      Vue.mixin({
        beforeCreate() {
          const rules = this.$options.validations;
          if (rules) {
            // 使用Object.assign合并computed
            this.$options.computed = Object.assign({}, this.$options.computed, {
              $v() {
                let valid = true;
                const errors = [];
                Object.keys(rules).forEach((key) => {
                  const rule = rules[key];
                  const value = this[key];
                  const result = rule.validate(value);
                  if (!result) {
                    valid = false;
                    errors.push(rule.message(key, value));
                  }
                });
                return {
                  valid,
                  errors,
                };
              },
            });
          }
        },
      });
    },
  };

需要注意的是,这里需要在原来的基础上添加$v,并且保留可能已经被定义的computed,所以使用了Object.assign来合并它们。

大功告成!

最后附上完整代码:

<div id="app">
  <form @submit="validate">
    <input v-model="text" />
    <br />
    <input v-model="email" />
    <ul v-if="!$v.valid" style="color: red">
      <li v-for="error in $v.errors">{{error}}</li>
    </ul>
    <input type="submit" :disabled="!$v.valid" />
  </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const emialRe =
    /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  const validationPlugin = {
    install(Vue) {
      Vue.mixin({
        beforeCreate() {
          const rules = this.$options.validations;
          if (rules) {
            // 使用Object.assign合并computed
            this.$options.computed = Object.assign({}, this.$options.computed, {
              $v() {
                let valid = true;
                const errors = [];
                Object.keys(rules).forEach((key) => {
                  const rule = rules[key];
                  const value = this[key];
                  const result = rule.validate(value);
                  if (!result) {
                    valid = false;
                    errors.push(rule.message(key, value));
                  }
                });
                return {
                  valid,
                  errors,
                };
              },
            });
          }
        },
      });
    },
  };
  Vue.use(validationPlugin);

  const app = new Vue({
    el: "#app",
    data: {
      text: "foo",
      email: "",
    },
    // 自定义的验证规则
    validations: {
      text: {
        validate: (value) => value.length >= 5,
        message: (key, value) =>
          `${key} should have a min length of length 5, bug got ${value.length}`,
      },
      email: {
        validate: (value) => emialRe.test(value),
        message: (key) => `${key} must be a valid email`,
      },
    },
    methods: {
      validate(e) {
        if (!this.$v.valid) {
          e.preventDefault();
          alert("not valid!");
        }
      },
    },
  });
</script>

转载自:https://juejin.cn/post/7049770996231831559
评论
请登录