1 Code Structure
2. Code implementation
2.1 the index. The vue
Implement test code
<template>
<div>
<JForm ref="submitForm" :model="model" :rules="rules">
<JFormItem label="Username" prop="username">
<JInput v-model="model.username" type="text" placeholder="Please enter user name 2"></JInput>
</JFormItem>
<JFormItem label="User password" prop="password">
<JInput v-model="model.password" type="password" placeholder="Please enter user password" ></JInput>
</JFormItem>
<JFormItem>
<button @click="onClick">submit</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:"Please enter user information"}]."password" : [{required:false.message:"Please enter user password"}].}}},methods: {
onClick() {
this.$refs.submitForm.validate(isVaild= > {
if(isVaild){
alert("Verification successful")}else {
alert("Verification failed")}}); }}},</script>
Copy the code
2.2 JForm. Vue
Technical point
- Use provide for cross-component object access
- Prop Records model form information and rules form verification rules
- This.$children iterates through the validate method of all child components and returns a Promise object. Returns true if the verification is successful
<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 is a successful callback from the home page
validate(callbackFn) {// Validates all formItems
let list = this.$children.filter(item= > item.prop)
let taskList = []
list.forEach(item= > {
taskList.push(item.validate())
})
// Determine if all promise objects are successful,
Promise.all(taskList).then(res= > {
callbackFn(true)
}).catch(e= > {
callbackFn(false)})}},}</script>
Copy the code
2.3 JFormItem. Vue
Technical point
- Verification is implemented through Async-Validator
- Inject: [‘form’], inject the provide form object
- Object property names {[this.prop] :rules} are shorthand for calculating property methods [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 () {
// Add a listening method for the child component to tell itself to start validation
this.$on("validate".() = > {
this.validate()
})
},
methods: {
// Return a Promise object
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>
Copy the code
2.4 JInput. Vue
Technical point
- V-bind =”$attrs” gets all prop passed in
- InheritAttrs: False does not display prop information where called
- @input=”onInput” :value=”value
- This. The parent. The parent. The parent. Emit and father component JFormItem interaction
<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)
// Every edit triggers validation
this.$parent.$emit('validate')}}}</script>
Copy the code
2.3 Code Optimization
Question:
- Because $chlidren and $parent are strongly associated with components, it is not easy to refactor and adjust the code later if the parent-child relationship changes.
1. Solve the problem that the parent Form traverses all formItems
Implementation steps
- Parent component itemList, add listener method “jFROm. addItem”, wait for child component instantiation to add itemList
- After the child component is instantiated, $Dispatch is called to distribute the itemList that registers itself with the parent component
- The parent component can directly iterate over all itemList child components
2. JInput calls validate of FormItem
Implementation steps
The child component calls $Dispatch directly and validates the parent component
Code implementation
- Define $boardcast to support infinitely searching down for the eventName broadcast method in $chLID
- Define $dispatch to find the eventName corresponding to $parent
- Determine if a custom componentName is found by matching componentName
// Register global methods
//1. Pass from child up
Vue.prototype.$dispatch = function(componentName,eventName,data) {
let parent = this.$parent || this.$root
let name = parent.$options.componentName
while(parent && (! name ||name ! == componentName)){// Can not find the same parent component name or undefined always look up
parent = parent.$parent
}
if(parent) {
parent.$emit.apply(parent,[eventName].concat(data));// The parent component invokes $emit to trigger its own $ON event}}//2. Passing from father to son is temporarily useless
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)); }}); }// New componentName attribute for the corresponding page to find a match
//JForm.vue
export default {
name : "jFrom" ,
componentName : "j-from".data() {
return {
itemList: [] // Add a collection object for all submodules to be fired at the same time
}
},
created () {
// Wait for jfromItem to register
this.$on("jFrom.addItem".(item) = > {
this.itemList.push(item)
})
},
methods: {
//callbackFn is a successful callback from the home page
validate(callbackFn) {// Validates all formItems
let taskList = []
this.itemList.forEach(item= > {
taskList.push(item.validate())
})
}
},
}
//JFormItem.vue
export default {
name : "JFormItem" ,
componentName : "j-from-item",
mounted () {
// FormItems loaded are automatically registered into the parent component's itemList
if(this.prop) {
this.$dispatch("j-from"."jFrom.addItem"[this]) $dispatch = [this] $dispatch = [this] $dispatch = [this]}}},//JInput.vue
export default {
methods: {
onInput(e) {
this.$emit('input',e.target.value)
// this.$parent.$emit("validate")
this.$dispatch('j-form-item'.'validate')// Go to the corresponding validate method under el-form-item}}},Copy the code