首先来看一下element给出的案例
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<script>
export default {
data() {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
age: ''
},
rules: {
pass: [
{ validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
age: [
{ validator: checkAge, trigger: 'blur' }
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
需要关注的点:
三个组件
el-form el-form-item el-input
校验规则在form组件中,具体的值绑定至input,通过操作form节点的.validate方法来实现校验,具体的数据和绑定看下面代码
<template>
<div>
<WWQBlock class="form">
<WWQForm ref="formItems" :model="formData" :rules="ruleValidate">
<WWQFromItem label="姓名 :" prop="value1">
<WWQInput
placeholder="请输入内容..."
inputType="text"
v-model="formData.value1"
>
</WWQInput>
</WWQFromItem>
<WWQFromItem label="密码 :" prop="value2">
<WWQInput
placeholder="请输入内容"
inputType="password"
v-model="formData.value2"
>
</WWQInput>
</WWQFromItem>
<WWQFromItem label="爱好 :" prop="value3">
<WWQSelect v-model="formData.value3" :data="selectData"></WWQSelect>
</WWQFromItem>
<div></div>
</WWQForm>
<WWQBlock>
<WWQButton
type="success"
size="small"
@click="changeButton"
radius="10px"
>提交
</WWQButton>
</WWQBlock>
</WWQBlock>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
formData: {
value1: "",
value2: "",
value3: "",
},
ruleValidate: {
value1: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
value2: [
{ required: true, message: "放心输入自己的隐私吧!", trigger: "blur" },
{ required: true, min: 0, max: 8, message: "超出限制了...",trigger: "change" },
],
value3: [
{ required: true, message: "总得有个特长什么的吧?", trigger: "blur" },
],
},
}
},
methods: {
changeButton() {
console.log(this.formData);
this.$refs.formItems
.validate() // 验证方法
.then((res) => {
console.log(res);
});
},
},
};
</script>
可以看到 el-form el-form-item el-input 这三个组件时互相嵌套关系,并非简单的父子组件,想要查看只能通过插槽,但是还有一种更灵活的办法,
那就是 provide 广播 和 inject 派发
首先需要定义一个事件总线,以供全局使用
function broadcast(componentName, eventName, params) {
// 循环子节点找到名称一样的子节点 否则 递归 当前子节点
this.$children.map((child) => {
if (componentName === child.$options.name) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat(params));
}
});
}
export default {
methods: {
/**
* 派发 (向上查找) (一个)
* @param componentName // 需要找的组件的名称
* @param eventName // 事件名称
* @param params // 需要传递的参数
*/
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root; //$parent 找到最近的父节点 $root 根节点
let name = parent.$options.name; // 获取当前组件实例的name
// 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点
// 循环出当前名称的一样的组件实例
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
// 有节点表示当前找到了name一样的实例
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
/**
* 广播 (向下查找) (广播多个)
* @param componentName // 需要找的组件的名称
* @param eventName // 事件名称
* @param params // 需要传递的参数
*/
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
},
},
};
在父组件我们这样做
provide() {
// 这里相当于把整个组件实例广播出去
return {
form: this,
};
},
子组件中通过inject获取
inject: ["form"],
这样一来三个组件可以互相通信,并且彼此更改组件之间的值,
form组件在接受到rule和model之后干了什么?
<template>
<form action="">
<slot></slot>
</form>
</template>
<script>
export default {
name: "WWQForm",
provide() {
return {
form: this,
};
},
data() {
return {
fields: [], // 储存当前的 form-item的实例
};
},
created() {
let that = this;
this.$on("on-form-item-add", (item) => {
if (item) {
that.fields.push(item);
}
});
this.$on("on-form-item-remove", (item) => {
if (item.prop) {
that.fields.splice(that.fields.indexOf(item), 1);
}
});
},
methods: {
resetFields() {
this.fields.forEach((field) => {
field.resetField();
});
},
validate(callback) {
return new Promise((resolve) => {
let valid = true; // 默认是通过
let count = 0; // 来匹配当前是否是全部检查完
this.fields.forEach((field) => {
// 每个实例都会有 validation 的校验的方法
field.validation("", (error) => {
if (error) {
valid = false;
}
// 通过当前检查完所有的form-item的时候才会调用
if (++count === this.fields.length) {
resolve(valid); // 方法使用then
if (typeof callback === "function") {
callback(valid); // 直接调用注入的回调方法
} }
});
});
}); }, },};
可以看到该组件广播了两个事件,并且定义了验证方法,遍历逐一校验表单项,
form-item做了啥?
<template>
<div class="wwq-form-item">
<label class="wwq-label" v-if="label" for="">
{{ label }}
</label>
<slot></slot>
<Transition name="tip">
<span class="wwq-form-item-message" v-if="validateState === 'error'">
{{ validateMessage }}
</span>
</Transition>
</div>
</template>
<script>
import Emitter from "../../emitter";
import schema from "async-validator";
export default {
name: "WWQFromItem",
mixins: [Emitter],
inject: ["form"],
props: {
label: String,
prop: String,
},
computed: {
fieldValue() {
return this.form.model[this.prop];
},
},
data() {
return {
initialValue: "", // 储存默认值
isRequired: false, // 当前的是否有问题
validateState: "", // 是否校验成功
validateMessage: "", // 校验失败文案
};
},
methods: {
setRules() {
let that = this;
let rules = this.getRules(); //拿到父组件过滤后当前需要使用的规则
if (rules.length) {
this.isRequired = rules.some((rule) => {
return rule.required;
});
}
this.$on("on-form-blur", that.onFieldBlur);
this.$on("on-form-change", that.onFieldChange);
},
getRules() {
let that = this;
let rules = that.form.rules;
rules = rules ? rules[that.prop] : [];
return [].concat(rules || []); //这种写法可以让规则肯定是一个数组的形式
},
onFieldBlur() {
this.validation("blur");
},
onFieldChange() {
this.validation("change");
},
getFilteredRule(trigger) {
let rules = this.getRules();
// !res.trigger 没有调用方式的时候默认就校验的
// filter 过滤出当前需要的规则
return rules.filter(
(res) => !res.trigger || res.trigger.indexOf(trigger) !== -1
);
},
validation(trigger, callback = function () {}) {
let rules = this.getFilteredRule(trigger);
// 判断当前是否有规则
if (!rules || rules.length === 0) {
return;
}
this.validateState = "validating";
var validator = new schema({ [this.prop]: rules });
// firstFields: true 只会校验一个
validator.validate(
{ [this.prop]: this.fieldValue },
{ firstFields: true },
(errors) => {
this.validateState = !errors ? "success" : "error";
this.validateMessage = errors ? errors[0].message : "";
callback(this.validateMessage);
}
);
},
resetField() {
this.form.model[this.prop] = this.initialValue;
},
},
// 组件渲染时,将实例缓存在 Form 中
mounted() {
// 如果没有传入 prop,则无需校验,也就无需缓存
if (this.prop) {
this.dispatch("WWQForm", "on-form-item-add", this);
// 设置初始值,以便在重置时恢复默认值
this.initialValue = this.fieldValue;
// 添加表单校验
this.setRules();
}
},
// 组件销毁前,将实例从 Form 的缓存中移除
beforeDestroy() {
this.dispatch("WWQForm", "on-form-item-remove", this);
},
};
</script>
通过自身接受的字段从父级获取表单绑定的value,并广播交互事件
input组件做的事
<template>
<div class="wwq-input">
<input
:type="inputType"
:value="defaultValue"
@input="handleInput"
@blur="handleBlur"
:placeholder="placeholder"
/>
</div>
</template>
<script>
import Emitter from "../../emitter";
export default {
name: "WWQInput",
mixins: [Emitter],
data() {
return {
defaultValue: this.inputVal,
};
},
props: {
inputType: {
type: String,
default: "text",
},
value: String,
placeholder: String,
},
watch: {
value(val) {
this.defaultValue = val;
},
},
methods: {
handleInput(event) {
this.defaultValue = event.target.value;
this.$emit("input", event.target.value);
// 将当前的值发送到 aiFormItem 进行校验
this.dispatch("WWQFromItem", "on-form-change", event.target.value);
},
handleBlur(event) {
// vue 原生的方法 return 出去
this.$emit("blur", event.target.value);
// 将当前的值发送到 aiFormItem 进行校验
this.dispatch("WWQFromItem", "on-form-blur", event.target.value);
},
},
};
</script>
表单交互事件派发给 form-item 组件,改变绑定value的同时触发校验
校验失败的话 form-item 会弹出警告信息。
注意:
绑定至父级节点form的rule一定要符合规范~
ruleValidate: {
value1: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
value2: [
{ required: true, message: "放心输入自己的隐私吧!", trigger: "blur" },
{ required: true, min: 0, max: 8, message: "超出限制了...",trigger: "change" },
],
value3: [
{ required: true, message: "总得有个特长什么的吧?", trigger: "blur" },
],
},