v-model

If you think it is good, please send me a Star at GitHub

V-model directives can be used to create two-way data binding on form elements such as input, SELECT, and so on, where changes in data drive view updates, which in turn affect data changes.

V – analytic model

In the directive section, we mentioned that V-model and V-show are global directives provided by Vue by default and can be used directly.

Since the V-model is a type of instruction, its parsing logic should be similar to that of a normal instruction. Note that we refer to ordinary instructions as distinct from V-bind and V-ON.

In this section, we use the following code as an example:

new Vue({
  el: '#app',
  data () {
    return {
      msg: 'Hello, msg'}},template: `<input v-model="msg" />`
})
Copy the code

During the Parse phase of the V-Model, it calls processAttrs in the processElement method to handle the various attributes parsed on the tag:

export function processElement (element: ASTElement, options: CompilerOptions) {
  / /... Omit code
  processAttrs(element)
  return element
}
Copy the code

We then return to the processAttrs method, which we mentioned repeatedly in the directive and event handling. Since v-model is a general instruction, we omit the code related to V-bind and V-ON:

export const dirRE = process.env.VBIND_PROP_SHORTHAND
  ? /^v-|^@|^:|^\.|^#/
  : /^v-|^@|^:|^#/
const argRE = / : (. *) $/
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      el.hasBindings = true
      // modifiers omit code
      if (bindRE.test(name)) {
        // v-bind omits the code
      } else if (onRE.test(name)) {
        // v-on omits code
      } else {
        // normal directives
        name = name.replace(dirRE, ' ')
        // parse arg
        const argMatch = name.match(argRE)
        let arg = argMatch && argMatch[1]
        isDynamic = false
        if (arg) {
          name = name.slice(0, -(arg.length + 1))
          if (dynamicArgRE.test(arg)) {
            arg = arg.slice(1, -1)
            isDynamic = true
          }
        }
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
        if(process.env.NODE_ENV ! = ='production' && name === 'model') {
          checkForAliasModel(el, value)
        }
      }
    } else {
      / /... Omit code}}}Copy the code

In the else branch, the value of name becomes model by first removing the V-prefix from the V-Model string using the dirRE regular expression. Next, it uses argRE regular expressions to match instruction parameters. To change our case slightly, here’s what it looks like:

const template = `<input v-model:value="msg" />`

// The matched instruction parameter
const arg = 'value'
Copy the code

After processing, call the addDirective method and add the cache property to the AST object:

export function addDirective (el: ASTElement, name: string, rawName: string, value: string, arg: ? string, isDynamicArg: boolean, modifiers: ? ASTModifiers, range? : Range) {
  (el.directives || (el.directives = [])).push(rangeSetItem({
    name,
    rawName,
    value,
    arg,
    isDynamicArg,
    modifiers
  }, range))
  el.plain = false
}
Copy the code

After the parse process is complete, the AST results are as follows:

const ast = {
  type: 1.tag: 'input'.attrsList: [{name: 'v-model'.value: 'msg'}].attrsMap: {
    'v-model': 'msg'
  },
  directives: [{name: 'model'.rawName: 'v-model'.value: 'msg'}}]Copy the code

GenDirectives are then called in the genData method to handle the directives in the CodeGen code generation phase:

export function genData (el: ASTElement, state: CodegenState) :string {
  let data = '{'
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ', '
  / /... Omit code
  return data
}

function genDirectives (el: ASTElement, state: CodegenState) :string | void {
  const dirs = el.directives
  if(! dirs)return
  let res = 'directives:['
  let hasRuntime = false
  let i, l, dir, needRuntime
  for (i = 0, l = dirs.length; i < l; i++) {
    dir = dirs[i]
    needRuntime = true
    const gen: DirectiveFunction = state.directives[dir.name]
    if (gen) {
      // compile-time directive that manipulates AST.
      // returns true if it also needs a runtime counterpart.needRuntime = !! gen(el, dir, state.warn) }if (needRuntime) {
      hasRuntime = true
      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
        dir.value ? `,value:(${dir.value}),expression:The ${JSON.stringify(dir.value)}` : ' '
      }${
        dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ' '
      }${
        dir.modifiers ? `,modifiers:The ${JSON.stringify(dir.modifiers)}` : ' '
      }}, `}}if (hasRuntime) {
    return res.slice(0, -1) + '] '}}Copy the code

Unlike the other directives, here we will look at the state-cache property, which is handled in the constructor of the CodegenState class: directives

export class CodegenState {
  options: CompilerOptions;
  warn: Function;
  transforms: Array<TransformFunction>;
  dataGenFns: Array<DataGenFunction>;
  directives: { [key: string]: DirectiveFunction };
  maybeComponent: (el: ASTElement) = > boolean;
  onceId: number;
  staticRenderFns: Array<string>;
  pre: boolean;

  constructor (options: CompilerOptions) {
    this.options = options
    / /... Omit code
    this.directives = extend(extend({}, baseDirectives), options.directives)
    / /... Omit code}}Copy the code

One of the options is related to the platform parameters, in a web browser side, the parameters defined in SRC/platforms/web/runtime options. Js file:

import directives from './directives/index'
export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules,
  directives,
  isPreTag,
  isUnaryTag,
  mustUseProp,
  canBeLeftOpenTag,
  isReservedTag,
  getTagNamespace,
  staticKeys: genStaticKeys(modules)
}
Copy the code

We are concerned with directives, which include V-text, V-HTML, and V-Model, and in this section we are only concerned with the contents of the V-Model related, both directives/model.js file.

export default function model (
  el: ASTElement,
  dir: ASTDirective,
  _warn: Function
): ?boolean {
  warn = _warn
  const value = dir.value
  const modifiers = dir.modifiers
  const tag = el.tag
  const type = el.attrsMap.type

  if(process.env.NODE_ENV ! = ='production') {
    // inputs with type="file" are read only and setting the input's
    // value will throw an error.
    if (tag === 'input' && type === 'file') {
      warn(
        ` <${el.tag} v-model="${value}" type="file">:\n` +
        `File inputs are read only. Use a v-on:change listener instead.`,
        el.rawAttrsMap['v-model'])}}if (el.component) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn't need extra runtime
    return false
  } else if (tag === 'select') {
    genSelect(el, value, modifiers)
  } else if (tag === 'input' && type === 'checkbox') {
    genCheckboxModel(el, value, modifiers)
  } else if (tag === 'input' && type === 'radio') {
    genRadioModel(el, value, modifiers)
  } else if (tag === 'input' || tag === 'textarea') {
    genDefaultModel(el, value, modifiers)
  } else if(! config.isReservedTag(tag)) { genComponentModel(el, value, modifiers)// component v-model doesn't need extra runtime
    return false
  } else if(process.env.NODE_ENV ! = ='production') {
    warn(
      ` <${el.tag} v-model="${value}"> : ` +
      `v-model is not supported on this element type. ` +
      'If you are working with contenteditable, it\'s recommended to ' +
      'wrap a library dedicated for that purpose inside a custom component.',
      el.rawAttrsMap['v-model'])}// ensure runtime directive metadata
  return true
}
Copy the code

In the Model method, we first determine that if we use v-model on the input tag of type=’file’, we will get an error message in the development environment because the attachment is read-only. The corresponding methods are then called based on the label type. In our case, it hits genDefaultModel, but we’ll cover the other branch logic in the corresponding section, just to get an idea.

Let’s downplay the rest of genDefaultModel’s logic and just look at the two core pieces of code:

function genDefaultModel (el: ASTElement, value: string, modifiers: ? ASTModifiers): ?boolean {
  / /... Omit code
  addProp(el, 'value'.` (${value}) `)
  addHandler(el, event, code, null.true)
  / /... Omit code
}
Copy the code

Code analysis:

  • addProp: calladdPropIs to giveastAdd avaluethepropsProperties.
  • addHandler: calladdHandlerIs to giveastAdd an event listener. What event does it listen forv-modelOn what tag.

In our example, having the two key pieces of code above is equivalent to writing it like this:

const template = '<input v-model="msg" />'
// Is equivalent to (slightly different)
const template = `<input :value="msg" @input="msg=$event.target.value" />`
Copy the code

From the above analysis, we can see that the V-Model handles bidirectional binding, which is essentially a syntax sugar that listens for user input events and updates the data, and does some special processing for extreme scenarios.

After that long detour, now let’s go back to the genData method and after calling the genDirectives method, there are more props and Events properties in the current AST object:

const ast = {
  type: 1.tag: 'input'.attrsList: [{name: 'v-model'.value: 'msg'}].attrsMap: {
    'v-model': 'msg'
  },
  directives: [{name: 'model'.rawName: 'v-model'.value: 'msg'}].props: [{name: 'value'.value: '(msg)'}]events: {
    input: 'if($event.target.composing)return; msg=$event.target.value'}}Copy the code

Because the AST object has props and events properties, the genData method handles the props and events events as well as directives:

export function genData (el: ASTElement, state: CodegenState) :string {
  let data = '{'
  // directive
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ', '
  / /... Omit code
  // DOM props
  if (el.props) {
    data += `domProps:${genProps(el.props)}, `
  }
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false)}, `
  }
  / /... Omit code
  return data
}
Copy the code

When genData method is finished, most of the work in codeGen code generation stage is finished, and the result of render function generated at last is as follows:

const render = ` with(this){ return _c('input',{ directives:[ { name:"model", rawName:"v-model", value:(msg), expression:"msg" } ], domProps:{ "value":(msg) }, on:{ "input":function($event){ if($event.target.composing)return; msg=$event.target.value } } }) } `
Copy the code

In this section, we spend a lot of time explaining how the V-Model is parsed and how to generate the corresponding render function based on the AST. We did this to save space in the next two sections, because v-Model parsing is basically the same for both form elements and components.

Binding form elements

In the section on binding form elements, we chose to categorize them selectively by form element.

  1. inputThe text box andtextareaThe text field.
  2. checkboxCheck box.

Input and textarea

The processing logic for the V-model on the input tag is the same as that for the Textarea tag. Let’s take the input tag as an example:

new Vue({
  el: '#app',
  data () {
    return {
      msg: 'Hello, msg',}},template: `<input v-model="msg" />`
})
Copy the code

After parsing, the AST results are as follows:

const ast = {
  type: 1.tag: 'input'.attrsList: [{name: 'v-model'.value: 'msg'}].attrsMap: {
    'v-model': 'msg'
  },
  directives: [{name: 'model'.rawName: 'v-model'.value: 'msg'}}]Copy the code

Then in the CodeGen code generation phase, genData is called to handle the directives, props properties, and events events:

export function genData (el: ASTElement, state: CodegenState) :string {
  let data = '{'
  const dirs = genDirectives(el, state)
  if (dirs) data += dirs + ', '
  / /... Omit code
  // DOM props
  if (el.props) {
    data += `domProps:${genProps(el.props)}, `
  }
  // event handlers
  if (el.events) {
    data += `${genHandlers(el.events, false)}, `
  }
  / /... Omit code
  data = data.replace($/ /,.' ') + '} '
  / /... Omit code
  return data
}
Copy the code

Code analysis:

  • genDirectives: called firstgenDirectivesMethod, in which a platform-specificmodelThe method is heremodelMethod is handled according to the type of element tag,inputortextareaThe label processing logic is as follows:
else if (tag === 'input' || tag === 'textarea') {
  genDefaultModel(el, value, modifiers)
}
Copy the code

Let’s look at the complete code for the genDefaultModel method:

function genDefaultModel (el: ASTElement, value: string, modifiers: ? ASTModifiers): ?boolean {
  const type = el.attrsMap.type

  // warn if v-bind:value conflicts with v-model
  // except for inputs with v-bind:type
  if(process.env.NODE_ENV ! = ='production') {
    const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
    const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
    if(value && ! typeBinding) {const binding = el.attrsMap['v-bind:value']?'v-bind:value' : ':value'
      warn(
        `${binding}="${value}" conflicts with v-model on the same element ` +
        'because the latter already expands to a value binding internally',
        el.rawAttrsMap[binding]
      )
    }
  }

  const { lazy, number, trim } = modifiers || {}
  constneedCompositionGuard = ! lazy && type ! = ='range'
  const event = lazy
    ? 'change'
    : type === 'range'
      ? RANGE_TOKEN
      : 'input'

  let valueExpression = '$event.target.value'
  if (trim) {
    valueExpression = `$event.target.value.trim()`
  }
  if (number) {
    valueExpression = `_n(${valueExpression}) `
  }

  let code = genAssignmentCode(value, valueExpression)
  if (needCompositionGuard) {
    code = `if($event.target.composing)return;${code}`
  }

  addProp(el, 'value'.` (${value}) `)
  addHandler(el, event, code, null.true)
  if (trim || number) {
    addHandler(el, 'blur'.'$forceUpdate()')}}Copy the code

GenDefaultModel doesn’t have to be very complicated. It does four things: exception handling, decorator handling, adding props properties, and adding events.

  • genProps: in the callgenDirectivesMethod as addedpropsProperty, so will be calledgenPropsMethods to deal withpropsProperties:
// [ { name: 'value', value: '(msg)', dynamic: undefined } ]
function genProps (props: Array<ASTAttr>) :string {
  let staticProps = ` `
  let dynamicProps = ` `
  for (let i = 0; i < props.length; i++) {
    const prop = props[i]
    const value = __WEEX__
      ? generateValue(prop.value)
      : transformSpecialNewlines(prop.value)
    if (prop.dynamic) {
      dynamicProps += `${prop.name}.${value}, `
    } else {
      staticProps += `"${prop.name}":${value}, `
    }
  }
  staticProps = ` {${staticProps.slice(0, -1)}} `
  if (dynamicProps) {
    return `_d(${staticProps}[${dynamicProps.slice(0, -1)}]) `
  } else {
    return staticProps
  }
}
Copy the code

In the genProps method, it iterates through the props array. Because we passed only one parameter and dynamic was undefined, we returned staticProps as follows:

const staticProps = '{"value":(msg)}'
Copy the code
  • genHandlers: Because of this part of the logic we are inEvent Event ProcessingThis has been mentioned repeatedly in the chapter, so we will not repeat it. This method returns the following result:
const result = `on:{ "input":function($event){ if($event.target.composing)return; msg=$event.target.value } }`
Copy the code

checkbox

When v-Model is applied to a single checkbox tag, v-Model binds a Boolean value. When v-Model is applied to multiple checkbox labels, v-Model binds an array.

Let’s start with a single checkbox tag:

new Vue({
  el: '#app',
  data () {
    return {
      checked: true,}},template: '
      
Check whether
' is checked
}) Copy the code

When parse completes parsing, the AST object for its input tag looks like this:

const ast = {
  type: 1.tag: 'input'.directives: [{name: 'model'.value: 'checked'.rawName: 'v-model'}}]Copy the code

During codeGen code generation, the genCheckboxModel method is called when the platform-dependent model method is called:

else if (tag === 'input' && type === 'checkbox') {
  genCheckboxModel(el, value, modifiers)
}
Copy the code

Where genCheckboxModel method code is as follows:

function genCheckboxModel (el: ASTElement, value: string, modifiers: ? ASTModifiers) {
  const number = modifiers && modifiers.number
  const valueBinding = getBindingAttr(el, 'value') | |'null'
  const trueValueBinding = getBindingAttr(el, 'true-value') | |'true'
  const falseValueBinding = getBindingAttr(el, 'false-value') | |'false'
  addProp(el, 'checked'.`Array.isArray(${value}) ` +
    `? _i(${value}.${valueBinding}`) > - 1 + (
      trueValueBinding === 'true'
        ? ` : (${value}) `
        : `:_q(${value}.${trueValueBinding}) `
    )
  )
  addHandler(el, 'change'.`var $$a=${value}, ` +
        '$$el=$event.target,' +
        `$$c=$$el.checked? (${trueValueBinding}) : (${falseValueBinding}); ` +
    'if(Array.isArray($$a)){' +
      `var $$v=${number ? '_n(' + valueBinding + ') ' : valueBinding}, ` +
          '$$i=_i($$a,$$v); ' +
      `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})} ` +
      `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})} ` +
    `}else{${genAssignmentCode(value, '$$c')}} `.null.true)}Copy the code

The genCheckboxModel method is not very complicated, but the arguments passed to addProp and addHandler are a bit trickier to understand. Let’s look directly at the render function generated by the input tag:

const render = ` _c('input',{ directives:[ { name:"model", rawName:"v-model", value:(checked), expression:"checked" } ], attrs:{ "type":"checkbox" }, domProps:{ "checked":Array.isArray(checked)? _i(checked,null)>-1:(checked) }, on:{ "change":function($event){ var $$a=checked,$$el=$event.target,$$c=$$el.checked? (true):(false); if(Array.isArray($$a)){ var $$v=null,$$i=_i($$a,$$v); if($$el.checked){ $$i<0&&(checked=$$a.concat([$$v])) }else{ $$i>-1&&(checked=$$a.slice(0,$$i).concat($$a.slice($$i+1))) } }else{ checked=$$c } } } } )`
Copy the code

Note: the _i utility function is similar to the _s and other utility functions mentioned earlier, which is short for looseIndexOf: looseIndexOf

/** * Return the first index at which a loosely equal value can be * found in the array (if value is a plain object, the array must * contain an object of the same shape), or -1 if it is not present. */
export function looseIndexOf (arr: Array<mixed>, val: mixed) :number {
  for (let i = 0; i < arr.length; i++) {
    if (looseEqual(arr[i], val)) return i
  }
  return -1
}
Copy the code

As you can see from the above code, although the template we wrote was simple, the render we generated was a bunch of code, and it should be clear that v-Model bidirectional binding is just syntactic sugar.

Binding component

Custom input components

In Vue2.2.0+, the v-model also supports a component. Let’s take the following code as an example to analyze:

Vue.component('child-component', {
  props: ['value'].template: `<input :value="value" @input="handleInput" />`.methods: {
    handleInput ($event) {
      this.$emit('input', $event.target.value)
    }
  }
})
new Vue({
  el: '#app',
  data () {
    return {
      msg: ' ',}},template: `<child-component v-model="msg" />`
})
Copy the code

Since the principle of v-Model bidirectional binding requires an attribute and an event listener, we provide a value attribute and an input event listener on top of the child component input tag in standard writing.

We have examined the parse logic of the child component in the previous section. The process is the same. Now let’s take a look at the parent component parse result:

const ast = {
  type: 1.tag: 'child-component'.directives: [{name: 'model'.rawName: 'v-model'.value: 'msg'}}]Copy the code

Moving on to the CodeGen stage, the parent component calls the genData directives to handle the directives, and then executes the platform-specific Model methods. Because in the parent component, the V-model operates on a component, so the following branch of logic is executed:

else if(! config.isReservedTag(tag)) { genComponentModel(el, value, modifiers)// component v-model doesn't need extra runtime
  return false
}
Copy the code

If you compare the source code, you can see that in the Model method, there is another branch of logic that also calls genComponentModel:

if (el.component) {
  genComponentModel(el, value, modifiers)
  // component v-model doesn't need extra runtime
  return false
}
Copy the code

To match the logic of the if branch, we simply change the parent component’s template:

const template = '<component v-model="msg" is="ChildComponent" />'
Copy the code

Back up, let’s look at the code for the genComponentModel method:

export function genComponentModel (el: ASTElement, value: string, modifiers: ? ASTModifiers): ?boolean {
  const { number, trim } = modifiers || {}

  const baseValueExpression = '$$v'
  let valueExpression = baseValueExpression
  if (trim) {
    valueExpression =
      `(typeof ${baseValueExpression} === 'string'` +
      `? ${baseValueExpression}.trim()` +
      ` :${baseValueExpression}) `
  }
  if (number) {
    valueExpression = `_n(${valueExpression}) `
  }
  const assignment = genAssignmentCode(value, valueExpression)

  el.model = {
    value: ` (${value}) `.expression: JSON.stringify(value),
    callback: `function (${baseValueExpression}) {${assignment}} `}}Copy the code

After calling the genComponentModel method, the current AST object has one more Model property:

const ast = {
  type: 1.tag: 'child-component'.directives: [{name: 'model'.rawName: 'v-model'.value: 'msg'}].model: {
    value: '(msg)'.expression: 'msg'.callback: 'function ($$v) {msg=$$v}'}}Copy the code

When the codeGen phase is over, the render function generated by the parent component is as follows:

const render = `_c('child-component',{ model:{ value:(msg), callback:function ($$v) { msg=$$v }, expression:"msg" } })`
Copy the code

Now that the render function has been generated, a component VNode will be generated during the patch phase by createComponent with SRC /core/vdom/create-component.js:

export function createComponent (
  Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? : string) :VNode | Array<VNode> | void {
  / /... Omit code
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  / /... Omit code
}
Copy the code

When executing the createComponent method, we call transformModel to handle this logic because we have the Model property.

function transformModel (options, data: any) {
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'; (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 {
    on[event] = callback
  }
}
Copy the code

Code analysis:

  1. propandevent: Gets components first in the codeoptions.modelProperty, default if not definedvalueorinput. The logic of this code says ifv-modelApplied to a component, we can provide to the componentmodelProperty to changepropsReceive properties and event names for dispatches, for example:
const parent = '<child-component v-model="msg" />'

const child = {
  props: ['value'].model: {
    prop: 'value'.event: 'change'
  },
  template: `<input type="checkbox" :value="value" @change="handleChange" />`.methods: {
    handleChange ($event) {
      this.$emit('change', $event.target.checked)
    }
  }
}
Copy the code
  1. on[event]: The logic of event processing is also very simple, determine whether the specified event distributed by the current component exists, if so, it will be processed according to whether the array form, if not, directly assign the value.

When the transformModel method completes execution, the expanded data object results as follows:

const data = {
  model: {
    callback: function ($$v) {
      msg = $$v
    },
    expression: 'msg'.value: ' '
  },
  attrs: {
    value: 'msg'
  },
  on: {
    input: function ($$v) {
      msg = $$v
    }
  }
}
Copy the code

Taking the example of our parent component, replacing it with a non-V-model form is equivalent to:

new Vue({
  el: '#app',
  data () {
    return {
      msg: ' ',}},template: `<child-component :value="msg" @input="msg=arguments[0]" />`
})
Copy the code

The sync modifier

In some cases, using v-Models on components is very convenient, but it also introduces new problems: since a child component can change the parent’s data, but there is no obvious source of change between the parent and the child, this introduces some maintenance issues for true bidirectional binding

To solve this problem, in Vue2.3.0+, the.sync modifier is provided, and we use $emit(‘update: XXX ‘) to emit events in the child component, for example:

const parent = '<child-component :value.sync="msg" />'

const child = {
  props: ['value'].template: `<input :value="value" @input="handleInput" />`.methods: {
    handleInput ($event) {
      this.$emit('update:value', $event.target.value)
    }
  }
}
Copy the code

Since sync is a modifier, if the sync modifier is removed, the parse phase of the above example will be the same as before and will not be described here. Let’s look directly at the processing logic for the sync modifier in the processAttrs method:

if (modifiers.sync) {
  syncGen = genAssignmentCode(value, `$event`)
  if(! isDynamic) { addHandler( el,`update:${camelize(name)}`,
      syncGen,
      null.false,
      warn,
      list[i]
    )
  }
}
Copy the code

As you can see, if the sync modifier is provided, an update: XXX event listener is added to the parent component. When the parse process is complete, the ast is generated as follows:

const ast = {
  type: 1.tag: 'child-component'.attrs: [{name: 'value'.value: 'msg'.dynamic: false}].attrsList: [{name: ':value.sync'.value: 'msg'}].attrsMap: {
    ':value.sync': 'msg'
  },
  events: {
    'update:value': {
      value: 'msg=$event'}}}Copy the code

When the codeGen code generation stage is completed, the generated render function results are as follows:

const render = ` with(this){ return _c('child-component',{ attrs:{"value":msg}, on:{ "update:value":function($event){ msg=$event } } }) } `
Copy the code

The modifier

.number and.trim modifiers

The handling of the. Number and. Trim modifiers is very simple, and the logic is as follows in the genDefaultModel method (similar elsewhere) :

const { number, trim } = modifiers || {}
let valueExpression = '$event.target.value'
if (trim) {
  valueExpression = `$event.target.value.trim()`
}
if (number) {
  valueExpression = `_n(${valueExpression}) `
}
Copy the code

When the. Number modifier is provided, it is wrapped with the _n utility function, which is an abbreviation for the toNumber method.

The lazy modifier

By default, v-Model synchronizes the value of the input field with the data each time an input event is triggered (except when the input method combines text). You can add the lazy modifier to synchronize after the change event.

Suppose we have the following case:

// normali
const normalTemplate = '<input v-model="msg" />'

// lazy
const lazyTemplate = '<input v-model.lazy="msg" />'
Copy the code

After codeGen code is generated, they generate the following render function on event portion:

// normal
const normalRender = ` on:{ input:function($event){ if($event.target.composing)return; msg=$event.target.value } } `

// lazy
const lazyRender = ` on:{ change:function($event){ msg=$event.target.value } } `
Copy the code

As described on the official website, with the lazy modifier, it changes from listening for input events to listening for change events.

summary

In the v-Model section, we first introduced the processing process of V-Model in the links of parse and CodeGen code generation in detail.

Next, we analyze the v-Model applied to form elements input, checkbox, and component.

We then described the handling of the sync modifier, the new way v-Model works on components.

Finally, we also analyze the common modifiers used with V-model, such as. Number,. Trim, and. Lazy.

If you think it is good, please send me a Star at GitHub