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
: calladdProp
Is to giveast
Add avalue
theprops
Properties.addHandler
: calladdHandler
Is to giveast
Add an event listener. What event does it listen forv-model
On 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.
input
The text box andtextarea
The text field.checkbox
Check 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 firstgenDirectives
Method, in which a platform-specificmodel
The method is heremodel
Method is handled according to the type of element tag,input
ortextarea
The 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 callgenDirectives
Method as addedprops
Property, so will be calledgenProps
Methods to deal withprops
Properties:
// [ { 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:
prop
andevent
: Gets components first in the codeoptions.model
Property, default if not definedvalue
orinput
. The logic of this code says ifv-model
Applied to a component, we can provide to the componentmodel
Property to changeprops
Receive 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
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