Vue插件最佳实践:封装一个自己的表单验证插件
插件通常用来为Vue添加全局功能,通过Vue.use(plugin)
即可使用插件。
那么如何写一个插件呢?插件可以是一个函数,也可以是一个提供install方法的对象。这里以对象为例:
const myPlugin = {
install(Vue){
Vue.mixin({
// ...
})
}
}
对象形式的插件必须提供 install
方法,install 方法调用时,会将 Vue 作为参数传入。
写插件涉及到Vue.mixin
API,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
来遍历展示所有的错误提示。
表单双向绑定了text
和email
,在validations中对要验证的属性声明对应的验证规则和提示文字,其中validate函数用于设定对应的验证规则,message函数用于返回验证失败时显示的提示文字。
我们要做的,就是编写validationPlugin
中的代码,实现一个$v
,使上面的表单验证代码能够正常运行。正常运行时应该如下图所示:
通过观察,我们可以发现访问$v
时,会获得一个包含valid和errors属性的对象。因此,可以在created
生命周期钩子中设置watch
,监听text
和email
的变化,对每个属性进行验证,然后将结果设置到$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