The form
Personal use el – form component development form editing scenario already had more than a year, has been based on el – dynamic form components form component encapsulates several times, the dynamic form components encapsulation namely wrote a set of full amount in advance the form template, and built-in validation logic, added some custom slot, so when using dynamic form components, Configuration items can be passed in to optionally load the desired base form components. At the same time, the basic form components have been simplified, such as the assimilation of select, radioGroup, checkGroup and other composite components, packaged into a higher-order component.
Problem solved: When developing editing scenarios where the interaction is not particularly complex, you can ignore validation, ignore composite components, and directly customize configuration items.
Disadvantages: Complex editing scenarios, such as policy editing, product editing, etc., are not supported by particularly complex interaction scenarios, because
-
There is no way to fully build in all the logic, validation rules, and templates.
-
Deep components are difficult to verify
Why is it so difficult to verify? Verification is a headache in form scenarios. It can be divided into synchronous verification, asynchronous verification, and linkage verification. If you use the el-Form, it is clear that if the el-Form implements validation, you need to pass in the path of the binding variable (prop) relative to the Model large object to the el-Form-Item component so that the built-in validation logic can find the value and verify it. There is a problem with this design:
- Pages are designed for dynamic linkage because of the need to meet the interaction, so prop is likely to need dynamic calculation, component level is deep, prop can not be found, verification failure is common things.
- Flexible scenarios are divided into independently maintained components, and then require maintaining a large object in the parent component, which may need to satisfy both UI and data design requirements, and two-way data binding between components (filling data during editing), which is difficult.
- The abstraction is difficult, and it is also difficult to bind the validation and base components, such as the grade, discipline, and system forms and their business relationships together, while requiring them to be built into any form scenario without affecting the layout of the host form, etc.
-
DDD driver, low reusability, public logic only mixin is not enough.
The above problems are not only the problems of EL-form but also the problems of VUE itself. As for the problems of VUE, the thinking of form scenes under VUE is summarized.
To sum up, using El-Form to write large form scenarios really makes me uncomfortable. There is a lot of repetitive work, and the configuration is very scattered. Therefore, it is urgent to come up with a form solution that fits the business of the company.
How is El-Form implemented
As a widely used open source library, every element of Element-UI is designed for a reason. Let’s take a look at how the El-Form component is designed:
Source code structure:
-- the form - item. Vue -- form. VueCopy the code
Let’s look at the core parts:
// template
<form class="el-form" :class="[
labelPosition ? 'el-form--label-' + labelPosition : '',
{ 'el-form--inline': inline }
]">
<slot></slot>
</form>
Copy the code
First, a form is a component that has a root node and a real DOM with its own style, rather than a function component that doesn’t instantiate. It can be called a function component. Therefore, in a form scenario, a Form component is both a function component and a container component.
// provide
provide() {
return {
elForm: this
};
},
Copy the code
Provide is a syntax sugar provided by Vue, along with inject: [‘elForm’], which is used by the form component to inject dependencies into all descendant components (the injected dependencies in the form component are actually instances of the form component itself).
The form component has two props (Model and Rules) and two important methods (clearValidate, VALIDATE (validateField), and resetFields).
reset
Let’s start by looking at how the form is reset:
// form.vue
resetFields() {
...
this.fields.forEach(field => {
field.resetField();
});
}
Copy the code
ResetField resetField resetField resetField resetField resetField
// form-item.vue resetField() {// Reset the validateState status this.validatEstate = "; this.validateMessage = ''; // Let model = this.form.model; // form-item will take a prop path. // Form-item will take a value from the path. // Form-item will take a value from the path. let path = this.prop; if (path.indexOf(':') ! == -1) { path = path.replace(/:/, '.'); } // getPropByPath returns an object: // o: tempObj, a reference to the parent of the binding value // k: keyArr[I] // v: tempObj? tempObj[keyArr[i]] : null let prop = getPropByPath(model, path, true); // this.validateDisabled = true; if (Array.isArray(value)) { prop.o[prop.k] = [].concat(this.initialValue); } else { prop.o[prop.k] = this.initialValue; }... }Copy the code
The form-item component caches the value of the prop binding (fieldValue) during the instantiation phase. Second, form-item is getting an object by getPropByPath, so let’s say I define an object obj that says:
let obj = {
a: {
b: {
c: 1
}
}
}
Copy the code
Pass obj to this function along with the relative A.B.C path where c is located, and you get:
{
o: { c:1 },
v: 1,
key: 'c'
}
Copy the code
Note that the o here is a reference to the b property in obj.
The form uses a hack approach to reset (reference passing), but it doesn’t follow the one-way data flow mechanism advocated by VUE. An object passed to the Form component through model has its mounted value changed implicitly.
The advantages of doing this are simple and violent resets, but there are disadvantages as well:
- Complex types cannot be reset, objects or arrays of objects cannot be reset, for example if we reset an array of panels, we may have to do so manually.
- It relies heavily on the dependency injection functionality provided by VUE. When a form component is nested within a form component, the outermost form component loses control of the inner component.
- You need to pass the path manually, of course, because of the design of separating the form component from the underlying component.
It’s not hard to imagine how form-Item has fared poorly when developing complex forms, thanks to prop. However, the purpose of this is also consistent with the design of a component library. The purpose of the form-item is to enhance the functionality of the base component. If we integrate functions such as reset validation into the base component, we break the single responsibility and make it difficult for users to extend the component.
check
Validation is a major bottleneck in the entire form scenario. First, using the native validation provided by Element-UI, many of the fixed input items are reformatted and difficult to reuse. Second, the validation itself relies heavily on prop properties.
Form-item props (prop, rules) Rules is the field validation rule passed in by the developer. The format can be:
{type: "string", Required: true,validator: (rule, value) => valu=== 'muji'}, trigger: 'blur' // or [{type: "string", Required: true,validator: (rule, value) => valu=== 'muji'}, trigger: 'blur' "string", required: true,validator: (rule, value) => valu=== 'muji',trigger:'blur'} ]Copy the code
El-form validation is implemented with the help of the async-validate library. Developers who know the library may know that the format of the rules parameter does not fully meet the requirements of the library, so form-Item first processes the rules and then calls the library for verification:
First, let’s look at the body logic that performs validation in the form-item:
// trigger represents the event type of the current function, validate(trigger, callback = noop) {this.validateDisabled = false; Const rules = this.getFilteredRule(trigger); const rules = this.getFilteredRule(trigger); // If ((! rules || rules.length === 0) && this.required === undefined) { callback(); return true; } // Verify that this. ValidateState = 'validating'; // Process data into an async-validate acceptable format const Descriptor = {}; if (rules && rules.length > 0) { rules.forEach(rule => { delete rule.trigger; }); } descriptor[this.prop] = rules; const validator = new AsyncValidator(descriptor); const model = {}; model[this.prop] = this.fieldValue; // Validate validator.validate(model, {firstFields: True}, (errors, invalidFields) => {// Change validation status // get validation prompt this.validateState =! errors ? 'success' : 'error'; this.validateMessage = errors ? errors[0].message : ''; // Execute callback(this. ValidateMessage, invalidFields); $emit('validate', this.prop,! errors, this.validateMessage || null); }); }Copy the code
The logic of validate method is relatively simple, which is to get corresponding rules according to trigger, call validator, pass in rules and values for verification, and finally return the verification results to the EL-Form component. This is actually very important. All of the el-Form’s descendant form-items return validation results to the El-Form, which is why you can call the el-Form’s validate method to retrieve the entire form validation result.
Let’s see how El-Form gets the validation results for all descendant components:
this.fields.forEach(field => { field.validate('', (message, field) => { if (message) { valid = false; } invalidFields = objectAssign({}, invalidFields, field); if ( typeof callback === 'function' && ++count === this.fields.length ) { callback(valid, invalidFields); }}); });Copy the code
This is a summation operation. When all descendant form-items are verified, the callback function is called. The logic is very simple.
Rules is an attribute that defines field validation rules, but rules can be defined on an EL-form or on a form-item. So what happens to the form-item if the rules field is defined in both places?
The processing of the rule is done inside the getFilteredRule function. Let’s look at the internal logic of this function:
Rules getRules() {let formRules = this.form.rules; const selfRules = this.rules; const requiredRule = this.required ! == undefined ? { required: !! this.required } : []; const prop = getPropByPath(formRules, this.prop || ''); formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : []; return [].concat(selfRules || formRules || []).concat(requiredRule); }, // according to trigger filter rules getFilteredRule(trigger) {const rules = this.getrules (); Return rules.filter(rule => {if (! rule.trigger || trigger === '') return true; if (Array.isArray(rule.trigger)) { return rule.trigger.indexOf(trigger) > -1; } else { return rule.trigger === trigger; } }).map(rule => objectAssign({}, rule)); }Copy the code
Rules is handled by retrieving the corresponding rule defined by the el-Form component based on the specified field (field path). Then filter according to trigger to get the final verification rule.
This is the verification process of EL-Form.