Prepend content

For Vue two-way binding we must not be unfamiliar, the basic things why to mention? Because this is the foundation of encapsulating components!

Here’s a question to ask 👇 try your basic 😏

pageIndex.vue

<template>
  <el-card>
 	  <dynamic-form v-model="testModel" />
  </el-card>
</template>
<script>
 export default {
   / /... Omit non-critical code
   data() {
     return {
       testModel: 'Init Value'}}}</script>
Copy the code

dynamic-form.vue

<template>
  <div>
    <el-input v-model="value"></el-input>
  </div>
</template>

<script>
export default {
  name: 'DynamicForm'.props: {
    value: {}}}</script>
Copy the code

Rendering 💗

The above code has two obvious problems:

  1. We are violating vUE’s single data stream and the child component should not change the parent component’s value
  2. After the value of the child component v-Model binding changes, the parent component does not achieve the effect of bidirectional binding

There are usually two solutions

Plan a

pageIndex.vue

- <dynamic-form v-model="testModel" />
+ <dynamic-form :value.sync="testModel" />
Copy the code

dynamic-form.vue

- <el-input v-model="value"></el-input>
+ <el-input v-model="newValue"></el-input>

+ export default {
+ computed: {
+ newValue: {
+ get({ value }) {
+ return value
+},
+ set(newVal) {
+ this.$emit('update:value', newVal)
+}
+}
+},
+}
Copy the code

Rendering 💗

This solution is used more often when we encapsulate business components based on open source component libraries. Here is why this is ok

We use the value of the parent component by calculating the property get. Set throws a new value upward, so that the child component does not change the value of the parent component directly. The parent component uses the.sync parent component to change the value of the child component

Sync syntax sugar is equivalent to this

pageIndex.vue

<dynamic-form :value.sync="testModel" />
<dynamic-form :value="testModel" @update:value="testModel = $event" />
Copy the code

This approach is equivalent, and the.sync approach is used a lot, often in the case of “component nesting”, but essentially it does the same thing and works the same way: for example, v-Model can customize the prop/event of the model

V – model

pageIndex.vue

<dynamic-form
  v-model="testModel"
  @customValue="testModel = $event"
/>
Copy the code

dynamic-form.vue

<template>
  <div>
    <el-input v-model="newValue"></el-input>
  </div>
</template>

<script>
export default {
  name: 'DynamicForm'.model: {
    prop: 'customValue'.event: 'customEvent'
  },
  props: {
    customValue: {},},computed: {
    newValue: {
      get({ customValue }) {
        return customValue
      },
      set(newVal) {
        this.$emit('customValue', newVal)
      }
    }
  },
}
</script>
Copy the code

Sync way

pageIndex.vue

<dynamic-form
  :value="testModel"
  :eventName="eventName"
  v-on:[eventName] ="testModel = $event"
/>

<script>
export default {
  data() {
    return {
      eventName: 'update:newValue',}}}Copy the code

dynamic-form.vue

<template>
  <div>
    <el-input v-model="newValue"></el-input>
  </div>
</template>

<script>
export default {
  name: 'DynamicForm'.model: {
    prop: 'customValue'.event: 'customEvent'
  },
  props: {
    customValue: {},},computed: {
    newValue: {
      get({ customValue }) {
        return customValue
      },
      set(newVal) {
        this.$emit('customValue', newVal)
      }
    }
  },
}
</script>
Copy the code

It’s easy to see that the principles are the same, so why design an API in Vue2 that does the same thing as the V-Model?

V-model can’t be used more than once on the same component! Sync is fine, I just throw a different ‘update: XXX ‘in the child component!

In Vue3, the V-model passes arguments to be used multiple times, and you can also customize modifiers, so this syntax is eliminated in Vue3. For details on Vue3, see 😶

Scheme 2

pageIndex.vue

<dynamic-form
  v-model="testModel"
/>
Copy the code

dynamic-form.vue

<el-input
  v-bind="$attrs"
  v-on="$listeners"
>
</el-input>
Copy the code

Rendering 💗

Principle of bidirectional binding: through v-bind=”$attrs”, the value of the parent component is transmitted to el-input to realize the value binding; V-on =”$Listeners “to transmit the @input events implemented internally in the V-Model to el-Input for value responses.

This is the most flexible way to encapsulate components, because in our daily work, the value of the V-Model bound to the custom component is usually not the value of the base type, we are most bound to the object, what will happen to the V-Model bound object?

Teach you how to gracefully encapsulate dynamic forms

The basic function

indexPage.vue

<template>
  <el-card>
    <dynamic-form
      v-model="formModel"
      v-bind="formConfig"
    />
  </el-card>
</template>

<script>
import DynamicForm from './common/DynamicForm'
export default {
  name: 'Model'.components: {
    DynamicForm
  },
  data() {
    return {
      formModel: {},
      formConfig: {
        labelWidth: '100px'.formItemList: [{label: 'Username'.type: 'input'.prop: 'userName'}, {label: 'password'.type: 'password'.prop: 'passWord'.'show-password': true
          },
          {
            label: 'note'.type: 'textarea'.prop: 'remark'.maxlength: 400.'show-word-limit': true.'auto-size': { minRows: 3.maxRows: 4}}},}</script>
Copy the code

dynamic-form.vue

<template>
  <el-form v-bind="$attrs">
    <template v-for="formItem of formItemList">
      <dynamic-form-item
        v-model="value[formItem.prop]"
        :key="formItem.prop"
        v-bind="formItem"
      />
    </template>
  </el-form>
</template>

<script>
import DynamicFormItem from './DynamicFormItem'
export default {
  name: 'DynamicForm'.props: {
    value: {
      type: Object.required: true
    },
    formItemList: {
      type: Array.default: () = >([])}},components: {
    DynamicFormItem,
  },
  created() {
    this.initFormItemValue()
  },
  methods: {
    initFormItemValue() {
      constformModel = { ... this.value }this.formItemList.forEach((item) = > {
        // Set the default value
        const { prop, value } = item
        if (formModel[prop] === undefined || formModel[prop] === null) {
          formModel[prop] = value
        }
      })
      this.$emit('input', { ...formModel })
    },
  }
}
</script>
Copy the code

dynamic-form-item.vue

<template>
  <el-form-item :label="label">
    <el-input
      v-if="inputType.includes(type)"
      :type="type"
      v-bind="$attrs"
      v-on="$listeners"
    >
    </el-input>
  </el-form-item>
</template>

<script>

/* These configuration items can be extracted into a separate configuration file */
const inputType = [/* Input supported types */]
const selectType = [/* select supported types */]
const customType = [/* Custom form item components */]

export default {
  name: 'DynamicFormItem'.props: {
    label: {
      type: String.required: true
    },
    type: {
      type: String.require: true.validator: (type) = > {
        return [...inputType, ...selectType, ...customType]
          .includes(type)
      }

    }
  },
  data() {
    return {
      inputType: Object.freeze(inputType),
    }
  },
}
</script>
Copy the code

Rendering 💗

Tips you can Get:

  • throughv-forTemplate reduces the creation of unnecessary DOM nodes and can also be implemented this wayv-for/v-ifUsed together
  • No responsive data is required to pass throughObject.freezeFrozen, Vue does not add this property when initializingget/set, which is also the point of performance optimization

Support depth attribute

In actual development, the properties of the form object may also be objects. Take a common CRM/ERP system for example, such as **[lease contract, deposit agreement, project setting]**, etc., these references to the master data are basically cascading references, this time the form component needs to support this function

indexPage.vue

<script>
export default {
  data() {
    return {
      formModel: {
        depositAgreement: {
          depositAmount: 100.businessType: 'traditionWork'.signDate: '2021-7-18'}},formConfig: {
        labelWidth: '100px'.formItemList: [{label: 'value'.type: 'number'.prop: 'depositAgreement.depositAmount'}, {label: 'Protocol Type'.type: 'select'.prop: 'depositAgreement.businessType'.options: [{dictKey: 'traditionWork'.dictValue: 'Dedicated office'},}, {label: 'Date of Signature'.type: 'depositAgreement.date'.prop: 'signDate',}]}}},}</script>
Copy the code

Rendering 💗

Supporting the depth attribute requires two methods

  • _getDeepAttr(model, deepPath)
  • _setDeepAttr(model, deepPath, val)
_getDeepAttr implementation
let data = {
  obj: {
    v1: 'v1-val'
  }
}
_getDeepAttr(data, 'obj.v1') // => v1-val
Copy the code
/ * * *@description: Depth acquisition property *@param {Object} Model Form object *@param {String} DeepPath Depth property *@return {any} * /
function _getDeepAttr(model, deepPath) {

  if(! deepPath)return
  if (deepPath.indexOf('. ')! = = -1) {
    const paths = deepPath.split('. ')
    let current = model
    let result = null
    for (let i = 0, j = paths.length; i < j; i++) {
      const path = paths[i]
      if(! current)break
      if (i === j - 1) {
        result = current[path]
        break
      }
      current = current[path]
    }
    return result
  } else {
    return model[deepPath]
  }
}
Copy the code
_setDeepAttr implementation
let data = {
  obj: {
    dep1: {
      v1: 'v1'.v2: 'v2'
    },
    name: 'north song'
  }
}
_setDeepAttr(data, 'obj.dep1.v1'.'v1-newVal') 
// data.obj.dep1.v1 => v1-newVal
// data.obj.dep1.v2 => v2
Copy the code
/ * * *@description: Sets the depth property *@param {Object} Model Form object *@param {String} DeepPath Depth property *@param {any} Val Specifies the value to be set */
function _setDeepAttr(model, deepPath, val) {
  / / path
  let paths = deepPath.split('. ')
  // The target value, which will hold all properties that match the path
  let targetVal = {}
  // Find the prop of each object successively
  let pathsNew = [...paths]
  let prop
  for (let i = paths.length - 1, j = i; i >= 0; i--) {
    prop = paths[i]
    // The value to be set at the last layer
    if (i === j) {
      targetVal[prop] = val
    } else if (i === 0) {
      // Get the root value first
      const originalVal = model[prop]
      // The root attribute of the first layer needs to be replaced directly
      model[prop] = Object.assign(originalVal, targetVal)
    } else {
      // Update values for each level (excluding stored values)
      let curDeppObj = _getDeepAttr(model, pathsNew.join('. '))
      // Store the values of the current hierarchy
      targetVal[prop] = Object.assign({}, curDeppObj, targetVal)
      // Delete the value stored in the previous path
      delete targetVal[paths[i + 1]]}// Remove the processed path
    pathsNew.pop()
  }
}
Copy the code

_getDeepAttr what good talk, here mainly talk about the implementation of _setDeepAttr idea

Implementation approach

  • Split path: Get the attribute name under each level, because you need to go down layer by layer to find the array of split levels for backup
  • Reverse loop: Our ultimate goal is to assign a value to the attributes of the last leveljTo determine if the current loop is the last layer
  • A judgment condition in circulatory body
    1. i === j: The last layer, which is the first loop, we will directlyTemporary objectsSet to the specified value
    2. i === 0At the first and last level of the loop, we read the model[first level property] and get the root property, which is obviously an object, and replace the root property with a resetTemporary objects
    3. Intermediate level processing: It is necessary to obtain the property object of the current level, merge the objects of the current level and temporary objects, only affecting the target attribute, other attributes of the same level should not be lost, and finally remove the attributes of the previous level

Adjustable to 💗

Strongly recommended to follow the debugging, a learn waste ~😃

dynamic-form.vue

<template>
  <el-form v-bind="$attrs">
    <template v-for="formItem of formItemList">
      <dynamic-form-item
- v-model="value[formItem.prop]"
+ :value="value[formItem.prop] | _formatterItemVal(value, formItem, _getDeepAttr)"
+ @input="bindItemValue(value, formItem, $event)"
        :key="formItem.prop"
        v-bind="formItem"
      />
    </template>
  </el-form>
</template>

<script>
import DynamicFormItem from './DynamicFormItem'
export default {
  name: 'DynamicForm',
  props: {
    value: {
      type: Object,
      required: true
    },
    formItemList: {
      type: Array,
      default: () => ([])
    }
  },
  components: {
    DynamicFormItem,
  },
- created() {
- this.initFormItemValue()
-},
+ filters: {
+ / * *
+ * @description:
+ * @param {any} curVal The value of the current form (null if depth attribute)
+ * @param {Object} value Form value
+ * @param {Object} item Configuration item of the current form
+ * @param {Function} _getDeepAttr Format method of item value
+ * @return {*}
+ * /
+ _formatterItemVal: (curVal, value, item, _getDeepAttr) => {
+ if (curVal) {
+ return curVal
+}
+
+ // Down is the case of the depth attribute, which needs to be formatted to get the value
+
+ // Provides the user with a method to format a value: trim, number, etc
+ const formater = item.formatter
+
+ return typeof formater === 'function'
+? formater(_getDeepAttr(value, item.prop))
+ : _getDeepAttr(value, item.prop)
+}
  },
  methods: {
- initFormItemValue() {
- const formModel = { ... this.value }
- this.formItemList.forEach((item) => {
- // Set the default value
- const { prop, value } = item
- if (formModel[prop] === undefined || formModel[prop] === null) {
- formModel[prop] = value
-}
-})
- this.$emit('input', { ... formModel })
-},

+ / * *
+ * @description: Implements bidirectional binding
+ * @param {Object} Model Form Object
+ * @param {String} deepPath
+ * @param {any} val Specifies the value to set
+ * /
+ bindItemValue(model, item, val) {
+ // The depth attribute needs to be formatted
+ if (~item.prop.indexOf('.')) {
+ this._setDeepAttr(model, item.prop, val)
+ } else {
+ model[item.prop] = val
+}
+
+ const _model = { ... model }
+ this.$emit('input', _model)
+}

+ _getDeepAttr(){ /*... * /}
+ _setDeepAttr(){/*... * /}
  }
}
</script>
Copy the code

dynamic-form-item.vue

<template>
  <el-input
    v-if="inputType.includes(type)"
    :type="type"
    v-bind="$attrs"
    v-on="$listeners"
  />
  <dynamic-select
    v-else-if="selectType.includes(type)"
    v-bind="$attrs"
    v-on="$listeners"
  />
  <el-date-picker
    v-else-if="dateType.includes(type)"
    v-bind="$attrs"
    v-on="$listeners"
  />
</template>
Copy the code

The dynamic-select component is a wrapped extension that allows select/treeSelect to dynamically retrieve options via a URL. See this column for more information

Custom content (Slots)

Front knowledge 👉 elaborate slot

After wrapping el-form, the wrapped components also need to support form-item slots. There are three:

The most customized part of our work is the form items. Like this: Using business-encapsulated components as form items

Whatever the component, the form item has to be customizable, and that’s where the slot comes in, right

  • Two rendering modes are supported
    1. Template Indicates the mode of a template
    2. How to write the render function in a configuration item

Priority: We follow the same rules as vue, the render function is superior to the template template

{
  label: 'Custom'.type: 'slot'.prop: 'custom'
}
Copy the code

Template writes slots

<dynamic-form
  v-model="formModel"
  v-bind="formConfig"
>
  <template #custom="{value}">
    <el-button>{{value}}</el-button>
  </template>
</dynamic-form>
Copy the code

How configuration items write the render function

{
  label: 'Custom'.type: 'slot'.prop: 'custom'.render: ({ value, $createElement: h }) = > {
    return h('el-button', value)
  }
}
Copy the code

dynamic-form.vue

The dynamic-form component changed little, adding this entry to inject the current instance down

export default {
  provide() {
    return {
      formThis: this}}},Copy the code

dynamic-form-item.vue

<template>
  <! -- Add support for type: slot -->
  <slot-content
    v-else-if="isRenderSlot({type, prop})"
    v-bind="$attrs"
    :render="generateSlotRender()"
  />
</template>

<script>
export default {
  / * * *@description: whether to render custom content *@param {String} type* /
  isRenderSlot({ type, prop }) {
    if(type ! = ='slot') {
      return false
    }
    /* Support two render modes: 1. How to write the render function in a configuration item */
    return [
      typeof this.formThis.$scopedSlots[prop],
      typeof this.$attrs.render
    ].includes('function')},// The priority of the render function to render custom content: how to write the render function in the configuration item > how to template
  generateSlotRender() {
    // normalizeScopedSlot 
    return ({ value, $createElement }) = > {
      // Pass parameters to the slot
      constslotScope = { ... this.$attrs, value, $createElement }const renderSlot = this.$attrs.render || this.formThis.$scopedSlots[this.prop]
      return renderSlot(slotScope)
    }
  },
}
</script>
Copy the code

Tips you can Get:

  • It takes longer to read than to write||It’s a little bit clearer
[
	typeof xxx,
  typeof xxxx,
].includes('function')

Copy the code
  • The method in the $scopeSlots slot is used to return VNode. We can pass parameters to pass data to the slot

Rendering 💗

Label/ERROR built-in slot support

Before implementing these two slots, you need to change the el-form-item to make it easier for users to configure slots using the depth attribute.

Let's say the user sets {prop: 'depositAgreement.depositAmount'. }Copy the code

What do users need to do when they want to customize form items using dynamic-form components

<! - depistiAmountSlot depistiAmountSlot is defined in the data variables: 'depositAgreement. DepositAmount - >
<template# [depistiAmountSlot] ></template> 

<! -- This is not the way -->
<template #depositAgreement.depositAmount></template>
Copy the code

When we use a template to write components, we compile everything we write. If there are special characters in the template, we need to do extra logic, so we can enjoy the shortcut of template syntax while losing some flexibility.

dynamic-form-item.vue

<template>
  <el-form-item
    :rules="rules"
    :label="label"
    :prop="prop"
  >
    <template
      v-if="formThis.$scopedSlots[realProp + 'Label']"
      #label
    >
      <slot-content
        v-bind="_attrs"
        :render="formThis.$scopedSlots[realProp + 'Label']"
      />
    </template>
    <template
      v-if="formThis.$scopedSlots[realProp + 'Error']"
      #error="{error}"
    >
      <slot-content
        v-bind="{... _attrs, error}"
        :render="formThis.$scopedSlots[realProp + 'Error']"
      />
    </template>
  </el-form-item>
</template>


<script>
export default {
  computed: {
    // The data passed to the slot
    _attrs({ value, label, rules, realProp, $attrs }) {
      return{ value, label, rules, realProp, ... $attrs } },Data. val => dataVal
    realProp({ prop }) {
      return prop.replace(/ \. ([^.] +) +? /g.(. arg) = > {
        const [, execProp] = arg
        return execProp[0].toUpperCase() + execProp.substr(1)})}}</script>
Copy the code

Rendering 💗

Form validation

Automatic validation support

If it is not a deep attribute, it can be implemented by adding model to e-form and prop/rules to el-form-item, but if it is a deep attribute, it needs to do some extra processing.

{
  label: 'value'.type: 'text'.prop: 'depositAgreement.depositAmount'.rules: [{required: true.message: 'The amount cannot be empty'.trigger: 'blur'
    },
    {
      validator: (rule, val, callback) = > {
        if (val < 100) {
          return callback(new Error('Amount not less than 100'))}return callback()
      },
      trigger: ['change'.'blur']]}},Copy the code

dynamic-form.vue

{
  computed: {
    // The item to be checked
    validateItem({ formItemList }) {
      return formItemList.filter(i= > i.rules)
    },
    // The data source of the passed form is used to handle the depth attribute verification problem
    model({ validateItem, value, isDeepPath, _getDeepAttr }) {
      const_model = { ... value }if(! validateItem.length)return _model;

      validateItem.forEach(({ prop }) = > {
        if (isDeepPath(prop)) {
          _model[prop] = _getDeepAttr(_model, prop, _getDeepAttr)
        }
      })
      return _model
    }
  },
}
Copy the code

Rendering 💗

The model we calculated is only used for the model attribute of El-Form, and only when the user sets the depth attribute and the verification model, the verification attribute will be calculated

Manual verification support

Just set the ref attribute to the el-form. For user customization, the ref is not written to death

{
  props: {
    // Customize elForm's ref attribute
    elFormRef: {
      type: String.default: 'elForm'}},methods: {
   / * * *@description: Validates the whole form */
    validate(callback) {
      return this.$refs[this.elFormRef].validate(callback)
    },
    / * * *@description: Single field verification */
    validateField(props, callback) {
      return this.$refs[this.elFormRef].validateField(props, callback)
    },
    / * * *@description: clears checksum */
    clearValidate(props) {
      return this.$refs[this.elFormRef].clearValidate(props)
    },
    / * * *@description: Form reset, clear validation */
    resetFields() {
      return this.$refs[this.elFormRef].resetFields()
    },
  }
}
Copy the code

In order to facilitate user operation, do not write the long $refs layer reference, let the user through the reference method

V-on =”$linsters” pit, be careful!

V -on=”$listeners”; v-on=”$listeners”

use

<dynamic-form
  ref="dynamicForm"
  v-model="formModel"
  v-bind="formConfig"
  @input="handleInput"
>
Copy the code

dynamic-form.vue

<dynamic-form-item
  :key="formItem.prop"
  :value="value[formItem.prop] | _formatterItemVal(value, formItem, _getDeepAttr)"
  v-bind="formItem"
+ v-on="$listeners"
  @input="bindItemValue(value, formItem, $event)"
/>
Copy the code

Take a closer look at this code, really no problem??

$listeners = $listeners = $listeners Is there a problem?

The story begins like this:

The process of interaction between El-Input and dynamic-form-item:

  1. We change the input field value
  2. El-input throws an input event and carries the value of the current form item base value of type Sting)
  3. The dynamic-form-item component writes v-ON =”$listeners” from EL-Input to send all events to El-Input
  4. We’re using dynamic-form-item and we actually write our own handler for the input event

Here we have implemented a bidirectional binding between dynamic-item and El-input

Dynamic-form-item and dynamic-form interaction

  1. Dynamic-form we update the value of the form object by listening for @input, which form item is currently changing, and throw an input event
  2. The user uses the input listening event and event handling method implemented internally by V-Model to help us realize two-way binding

The story here is very complete.

But I wrote an @input event outside myself, and this event was leaked to el-Input! So if the form item changes I have to listen for the v-Model’s internal implementation of the input event handling not to be reassigned?

The result internally reassigns the formModel in v-Model =”formModel”, from an object to the base value (the value thrown by el-Input).

That’s where the story ends. The page has an error

So dynamic-form has two Input events and it’s a program. How do you see that? Dynamic-form print this. Let’s see

dynamic-form.vue

A little bit of a makeover

export default {
  // Resolve the issue of double binding for $Listeners
  model: {
    // Customize the event name of the V-Model listener
    event: 'dyInput'}},Copy the code

If we toss the event, we’ll do it this way

this.$emit('dyInput', {... value})Copy the code

Rendering 💗

At this point, the function is complete, but the external listener input event can not be specific to the form item, this event is basically useless! We need to put a flag on the outside to tell the outside world that it was that form item that threw the event.

There is a problem, the form item as projects grow sure type more and more, the event type is more and more, a lot of form item throw event names is the same, the outside is not too good to control these events, all events are written in the book of this component, and want to distinguish between events exactly which component have to throw out the input output inside the event? That’s not good…

The sample code

<dynamic-form
  ref="dynamicForm"
  v-model="formModel"
  v-bind="formConfig"
  @input="handleInput"
  @change="handleChange"
  @visible-change="handleVisibleChange"
  @.
/>
Copy the code

We want only the input/change event on the dynamic-form, and all other events to be written in their respective configuration items

{
  label: 'Protocol Type'.type: 'select'./ /... Omit the options,props configuration
  prop: 'depositAgreement.businessType'.listeners: {
    'visible-change': (isShow) = > {
      console.log(isShow, 'select'); }}},Copy the code

dynamic-form.vue

<dynamic-form-item
  v-on="_listeners"
/>

<script>
export default {
  computed: {
    // Events that can be listened for in dynamic-form
    _listeners({ $listeners }) {
      // Support forward transparently transmitted events
      let supportEvent = ['input'.'change']
      return supportEvent.reduce((_listeners, eventName) = > {
        _listeners[eventName] = $listeners[eventName] || (() = >{})return _listeners
      }, {})
    }
  }
}
</script>
Copy the code

dynamic-form-item.vue

<dynamic-form-item
  v-on="_listeners"
/>

<script>
export default {
  computed: {
    // Integrate the listeners in the configuration item, and finally transmit the events transparently downward
    onEvent({ $listeners, listeners }) {
      // Events in configuration items have a higher priority than events listened for in dynamic-form
      return{... $listeners, ... listeners } } } }</script>
Copy the code

Extension: V – Model source analysis

transform component v-model data into props & events

src\core\vdom\create-component.js

// transform component v-model data into props & events
// V-model special processing
if (isDef(data.model)) {
  transformModel(Ctor.options, data)
}
Copy the code

transformModel

// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
  /* model: { prop: 'xx', event: 'xxx, } */
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
  // Assign a value to the attrs prop of the specified model; (data.attrs || (data.attrs = {}))[prop] = data.model.valueconst on = data.on || (data.on = {})
  const existing = on[event]
  const callback = data.model.callback
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1: existing ! == callback ) { on[event] = [callback].concat(existing) } }else {
    // Add a built-in event to the event
    on[event] = callback
  }
}
Copy the code

This is the end of this article. Most of the form components have been implemented, and the form layout has not been implemented. In fact, you need to use the el-Row /el-col package for the form items, and there is no special logic to post the code

Write in the last

If there is a piece of writing in the article is not very good or there are questions welcome to point out, I will also keep modifying in the following article. I hope I can grow with you as I progress. Those who like my article can also pay attention to it

I’ll be grateful to the first people to pay attention. At this time, you and I, young, packed lightly; And then, rich you and I, full of it.

Practice makes perfect, shortage in one

series

Render function “Component encapsulation-dynamic-select”

Render function “Component encapsulation-dynamic-input”

Render function “Component encapsulation-dynamic-checkbox”

Teach you how to play the render function “Component encapsulation-dynamic-Cascader”

The articles

The idea of modularity is to build the middle and background projects

The first chapter is to develop the background project with modularity

【 Front-end system 】 Talk about understanding EventLoop from an interview question (updated analysis of four advanced questions)

[Front end system] Build a great oaks from the foundation

[Front-end System] The application scenario of re in development is not just rule verification

“Functional programming practical scenario | nuggets technical essay – double festival special article”

【 suggested favorites 】 CSS obscure points are all here