Foreword
Today we will discuss the
form
form component. The open sourceform
form components on the market are basically written in the same way. Aform
component needs to write a lot ofform-item
components inside. Embeddedselect
,radio
,checkbox
, etc. These need to write loops, and you need to write a bunch ofclass
during layout. A slightly largerform
component can easily have 100-200 lines, or even more many.
Writing in this way is not only inconvenient to maintain, but also easy to dizzy, which obviously does not match the concept of Vue’s data-driven view. Therefore, we repackage theform-item
component. Why not repackage theform
form? Read on.
Problem Analysis
- When there are too many page elements, the amount of form-item code becomes larger, which is not conducive to maintenance.
- When the data type of the form-item sub-item is list, the writing method is not convenient, and various v-for and keys need to be used in HTML.
- When you need to use four-column equal-width arrangement layout, you need to write the corresponding col class in each position.
- When the length of the label field changes, you need to manually adjust the width of the label-width.
- In order to maintain consistent width and styling of embedded elements, heavy use of CSS styles is required.
- Configuring form validation rules is a cumbersome process.
- When the entire form needs to be completely disabled or all have clearable attributes, it is more troublesome to write.
- The cost of implementing dynamic display/hide of some form-items is high, and the code readability is poor.
Solution ideas
Let me first talk about why the form-item component is extracted instead of the form component.
Optimize the use of form
- Validation method: Form form validation can be performed through the this.$refs.form.validate method. If the form is separated, the validation will be more complicated and difficult.
- Property extraction: The form form rarely has content that can be extracted, and its parameters are basically written on the label, and the coding cost is low. If it is embedded in the component, it may cause form and form- The item parameter conflicts.
- Strong inclusiveness: The internal situation of the form is relatively complicated, so it needs to be more inclusive. If it is embedded inside the component, the component will be too restrictive and the coupling will increase.
Get the code structure by traversing
We see this code snippet:
<el-form-item label="name" prop="name"> <el-input v-model="form.name" placeholder="Please enter the name"></el-input> </el-form-item> <el-form-item label="gender prop="sex"> <el-select v-model="form.sex" placeholder="Please select gender"> <el-option label="Male" value="1"></el-option> <el-option label="Female" value="0"></el-option> </el-select> </el-form-item>
Optimize duplicate code ideas
According to the principle that repeated code can be optimized, we can establish the following ideas:
- Observe the n form-items in the form, and find that each item is wrapped in el-form-item;
- Consider setting the entire form-item as an array, and each array item corresponds to a form-item;
- Each form-item contains label and prop, so we can set the following initial parameters:
props: {<!-- --> config: {<!-- -->type: Array, default: () => [ {<!-- -->label: "name", prop: "name"}, {<!-- -->label: "gender", prop: "sex"} ]} }
We can iterate over the page
<el-form-item v-for="(item, index) in config" :key="'form-item' + index" :label="item.label" :prop="item.prop"> </el-form-item>
We can get:
<el-form-item label="name" prop="name"></el-form-item> <el-form-item label="gender prop="sex"></el-form-item>
Next, we noticed that the name is an input box (input
), gender is a drop-down box (select
), and there may be radio buttons (radio
), checkbox (checkbox
), time picker (date-picker
), datetime picker and time period picker (daterangepicker
) and other subcomponents. Faced with this situation, we can add corresponding parameters in config
, I named it itemType
(originally wanted to name it type
, but due to type
is too common to cause conflicts). Therefore, the current data entry parameters are:
props: {<!-- --> form: {<!-- --> type: Object, required: true, default: () => {<!-- -->} }, // shared form value object passed in config: {<!-- -->type: Array, default: () => [ // The optional itemType value is the possible high-frequency component name // input\select\radio\checkbox\date-picker, etc. (the components need to be written clearly, and need to add mandatory verification in require) {<!-- -->itemType: "input", label: "name", prop: "name"}, {<!-- -->itemType: "select", label: "gender", prop: "sex"} ]} }
Next, we will encounter a problem when developing the page. How do we put the corresponding form components (input, select, etc.) into it? The simplest and rude way is v-if v-else-if v-else , the disadvantage of this method is that each sub-content may have different logic, including dom will have a big difference, putting them all on one page will lead to a huge number of lines of code on a page, and it is extremely difficult to maintain, which does not conform to the requirements of a single file. The code principle of more than 200 lines (students are interested in this point, you can write a detailed version in the comment area @我, after the component library series is completed). So what we use here is to extract all the sub-components, introduce them through import, and inject components locally. Finally, different components are rendered in the form of components +: is. The code and structure picture are as follows:
<el-form-item v-for="(item, index) in config" :key="'form-item' + index" :label="item.label" :prop="item.prop"> <components :is="'item-' + item.itemType || 'text'" :form="form" v-bind="{ ...item, noLabel }" /> </el-form-item>
In components, form needs to be passed in for two-way binding of subcomponent values, and other parameters in the corresponding item of config also need to be passed in, for example, select requires multiple selections, then the config of the corresponding item: {itemType: ” select”, label: “gender”, prop: “sex”, multiple: true}, and then transparently transmit data through v-bind=”$attrs” at the sub-component layer to achieve compatibility with all elementUI primitives The purpose of the component function
Exception handling
Handle form items that do not match: use Slot
In business development, various strange situations often occur in form items. What if our child components cannot meet these requirements? At this time, you need to use a strong business solution – Slot.
When using Slot, we must first clarify:
- Which components require sockets
- How to make sure the socket is inserted correctly into the component
- How to get the corresponding data in the slot
On the basis of solving the above problems, we can efficiently and flexibly deal with the situation that the form items do not meet the requirements. It is worth noting that our basic component library should contain as little business logic as possible.
The thinking is clear, the problem is solved, let’s code happily together!
1. Which one needs the slot?
We can add an optional parameter “isSlot” with a value of Boolean in config, the situation is as follows:
props: {<!-- --> form: {<!-- --> type: Object, required: true, default: () => {<!-- -->} }, // shared form value object passed in config: {<!-- -->type: Array, default: () => [ // The optional itemType value is the possible high-frequency component name // input\select\radio\checkbox\date-picker, etc. (the components need to be written clearly, and need to add mandatory verification in require) {<!-- -->itemType: "input", label: "name", prop: "name"}, {<!-- -->itemType: "select", label: "gender", prop: "sex"}, {<!-- -->itemType: "select", label: "ID Card Slot", prop: "idCard", isSlot: true} ]} }
2. How to ensure that the embedded slot is placed in the specified position
When traversing, if the current parameter item.isSlot is false or does not exist, then we use components is
If it exists, we use the slot tag to specify the location of the slot content by means of a named slot. The name of the named slot uses the value of item.prop, and the result is as follows:
<el-form-item v-for="(item, index) in config" :key="'form-item' + index" :label="item.label" :prop="item.prop"> <components v-if="!item.isSlot" :is="'item-' + item.itemType || 'text'" :form="form" v-bind="{ ...item, noLabel }" /> <slot v-else :name="item.prop" /> </el-form-item>
3. How to get the data of the corresponding item in the slot
We can pass data in and out of slots through scoped named slots. code show as below
<el-form-item v-for="(item, index) in config" :key="'form-item' + index" :label="item.label" :prop="item.prop"> <components v-if="!item.isSlot" :is="'item-' + item.itemType || 'text'" :form="form" v-bind="{ ...item, noLabel }" /> <slot v-else v-bind="item" :name="item.prop" /> </el-form-item>
In this way, our form-item component supports the insertion of subcomponents and the compatibility of abnormal business so far.
Finally, post the final code and effect diagram of the form-item component (due to security issues, the fields have been deleted and the prop value has been modified):
<el-form ref="form" :model="form" :rules="rules"> <hc-form-item :form="form" :rules="rules" :config="formConfig" :column="3" clearable same-label class="mt-20\ "> <com-upload slot="certPositive" v-model="form.certPositive" ext=".jpg,.jpeg,.png,.gif" /> </hc-form-item> </el-form>
computed: {<!-- --> formConfig() {<!-- --> return [ {<!-- --> itemType: 'input', label: 'ID card number', prop: 'aa', readonly: this.type !== 'save' }, {<!-- --> itemType: 'input', label: 'name', prop: 'bb', readonly: this.type !== 'save' }, {<!-- --> itemType: 'radio', label: 'gender', prop: 'cc', code: 'staff_extension_sex' }, {<!-- --> itemType: 'date-picker', type: 'date', label: 'date of birth', prop: 'dd', 'value-format' : 'yyyy-MM-dd', disabled: true }, {<!-- --> itemType: 'input', label: 'Contact Number', prop: 'ee' }, {<!-- --> itemType: 'input', label: 'mailbox', prop: 'ff' }, {<!-- --> itemType: 'select', label: 'native place', prop: 'hh', code: 'native_place' }, {<!-- --> itemType: 'select', label: 'education/education level', prop: 'oo', code: 'staff_extension_education' }, {<!-- --> itemType: 'date-picker', type: 'date', label: 'date of residence permit processing', prop: 'residentPermitDate', 'value-format ': 'yyyy-MM-dd' }, {<!-- --> itemType: 'input', label: 'certificate front photo', prop: 'certPositive', isSlot: true }, ]; } } </code><img class="look-more-preCode contentImg-no-view" src="//i2.wp.com/csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreBlack. png" alt="" title="">
PS: We will gradually optimize the code packaging of form-item to solve the eight problems raised before. At the same time, a blog with related content is being produced, so stay tuned.
Component library code address
https://gitee.com/yangxiongasin/hc-basic
Component library document code address
https://gitee.com/yangxiongasin/hc-docs
If you are interested in component library development, you can enter the QQ group: 617330944 to discuss and exchange with everyone