1代码结构
2.代码实现
2.1 index.vue
实现测试代码
<template>
<div>
<JForm ref="submitForm" :model="model" :rules="rules">
<JFormItem label="用户名" prop="username">
<JInput v-model="model.username" type="text" placeholder="请输入用户名2"></JInput>
</JFormItem>
<JFormItem label="用户密码" prop="password">
<JInput v-model="model.password" type="password" placeholder="请输入用户密码" ></JInput>
</JFormItem>
<JFormItem>
<button @click="onClick">提交</button>
</JFormItem>
</JForm>
</div>
</template>
<script>
import JForm from "./JForm";
import JFormItem from "./JFormItem";
import JInput from "./JInput";
export default {
components: {
JForm,
JFormItem,
JInput
},
data() {
return {
model:{
username:'',
password:'',
},
rules: {
"username" : [{required:true,message:"请输入用户信息"}],
"password" : [{required:false,message:"请输入用户密码"}],
}
}
},
methods: {
onClick() {
this.$refs.submitForm.validate(isVaild => {
if(isVaild){
alert("验证成功")
}else {
alert("验证失败")
}
});
}
},
}
</script>
2.2 JForm.vue
技术点
- 使用provide做跨部件对象访问
- prop记录model表单信息和rules表单校验规则
- this.$children遍历所有子组件formItem的validate方法,并且返回promise对象,通过promise.all做批量处理。都成功才返回true 校验成功
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
name : "JForm",
provide () {
return {
form :this
}
},
props: {
model: {
type: Object,
required : true
},
rules: {
type: Object,
required : false
}
},
methods: {
//callbackFn是主页面调用成功的回调
validate(callbackFn) {//校验所有formItem的验证
let list = this.$children.filter(item=> item.prop)
let taskList = []
list.forEach(item => {
taskList.push(item.validate())
})
//判断所有promise对象是否成功,
Promise.all(taskList).then(res => {
callbackFn(true)
}).catch(e => {
callbackFn(false)
})
}
},
}
</script>
2.3 JFormItem.vue
技术点
- 通过async-validator实现校验
- inject : ['form'],注入provide的form对象
- 对象属性名{[this.prop] :rules } 使用计算属性方式[this.prop]简写
<template>
<div>
<label v-if="label">{{label}}</label>
<slot></slot>
<p class="error">{{error}}</p>
</div>
</template>
<script>
import Validator from "async-validator";
export default {
inject : ['form'],
name : "JFormItem",
props: {
label: {
type: String,
default: ''
},
prop: {
type: String,
default: ''
},
},
data() {
return {
error: ''
}
},
mounted () {
//添加监听方法用于子组件通知自己要开始校验
this.$on("validate", () => {
this.validate()
})
},
methods: {
//返回一个promise对象
validate() {
let rules = this.form.rules[this.prop]
let value = this.form.model[this.prop]
let validator = new Validator({[this.prop] :rules })
return validator.validate({[this.prop]:value}, errors => {
if(errors) {
this.error = errors[0].message
}else {
this.error = ""
}
} )
}
},
}
</script>
<style scoped>
.error {color: red;}
</style>
2.4 JInput.vue
技术点
- v-bind="$attrs" 获取传入的所有prop
- inheritAttrs:false 在调用的地方不显示prop信息
- @input="onInput" :value="value" 自己实现v-model效果
- this.emit与父组件JFormItem交互
<template>
<div>
<input :type="type" @input="onInput" :value="value" v-bind="$attrs" >
</div>
</template>
<script>
export default {
inheritAttrs:false,
name : "JInput" ,
props: {
value: {
type: String,
default: ""
},
type: {
type: String,
default: ""
},
},
methods: {
onInput(e) {
this.$emit('input',e.target.value)
// 每次编辑都触发校验
this.$parent.$emit('validate')
}
},
}
</script>
2.3代码优化
问题:
- 由于使用 $chlidren 和 $parent 与组件产生强关联,一旦父子关系变化,不便于后期重构与代码调整。
1.解决父组件Form遍历所有FormItem问题
实现步骤
- 父组件itemList,添加监听方法“jFrom.addItem”,等待子组件实例化后加入进来itemList
- 子组件实例化后,调用$dispatch派发把自己 注册到父组件的itemList
- 父组件就可以直接遍历所有itemList子组件
2.解决jInput调用FormItem的validate的校验方法
实现步骤
子组件直接调用$dispatch派发,父组件validate方法校验
代码实现
- 定义$boardcast 支持无限往下查找$chlid中对应eventName广播方法
- 定义$dispatch 往上查找$parent对应eventName的派发方法
- 通过匹配componentName 自定义组件名称来确定是否找到
//注册全局方法
//1. 从子往上传递
Vue.prototype.$dispatch = function(componentName,eventName,data) {
let parent = this.$parent || this.$root
let name = parent.$options.componentName
while(parent && (!name ||name !== componentName)){//找不到相同的父级组件名字或者未定义一直往上找
parent = parent.$parent
}
if(parent) {
parent.$emit.apply(parent,[eventName].concat(data));//父组件调用$emit 触发自己定义的$on事件
}
}
//2. 从父往子传递 暂时没用到
Vue.prototype.$boardcast = function(componentName,eventName,data) {
boardcast.call(this,componentName,eventName,data);
}
function boardcast(componentName,eventName,data) {
this.$children.forEach(child => {
let name = child.$options.componentName
if(name === componentName) {
child.$emit.apply(child,[eventName].concat(data));
} else {
boardcast.apply(child,[componentName,eventName].concat(data));
}
});
}
//对应页面新增componentName属性用于 寻找时匹配
//JForm.vue
export default {
name : "jFrom" ,
componentName : "j-from",
data() {
return {
itemList: [] // 新增所有子模块的集合对象,用于校验时候一起触发
}
},
created () {
//等待jfromItem注册进来
this.$on("jFrom.addItem", (item) => {
this.itemList.push(item)
})
},
methods: {
//callbackFn是主页面调用成功的回调
validate(callbackFn) {//校验所有formItem的验证
let taskList = []
this.itemList.forEach(item => {
taskList.push(item.validate())
})
}
},
}
//JFormItem.vue
export default {
name : "JFormItem" ,
componentName : "j-from-item",
mounted () {
//加载完的formItem都自动注册进入父组件的itemList
if(this.prop) {
this.$dispatch("j-from","jFrom.addItem",[this]) //这里由于$dispatch使用的是 apply 所以参数需要是数组,使用[this]
}
},
}
//JInput.vue
export default {
methods: {
onInput(e) {
this.$emit('input',e.target.value)
// this.$parent.$emit("validate")
this.$dispatch('j-form-item', 'validate')//往上找到对应的el-form-item下的validate方法
}
},
}