v-model
v-model
v-on
Like the previously analyzed , they are all Vue
instructions provided by , v-model
so the analysis process of is v-on
similar to that of . Around the compilation of the template, render
the generation of the function, and the mounting of the final real node. v-model
No matter what the usage scenario is, it is essentially a syntactic sugar.
basic use
v-model
It is inseparable from the form. The reason why the view can affect the data is that the view is essentially interactive, so the form is the prerequisite for this interaction. The use of the form takes <input> <textarea> <select>
as the core, let’s look at the specific usage
// 普通输入框
<input type="text" v-model="value1">
// 多行文本框
<textarea v-model="value2" cols="30" rows="10"></textarea>
// 单选框
<div class="group"><input type="radio" value="one" v-model="value3"> one<input type="radio" value="two" v-model="value3"> two
</div>
Let's first review the process from the template to the real node.
- 1. The template is parsed into
AST
a tree - 2.
AST
Tree generation executablerender
function generation - 3.
render
Convert functions to virtualDOM
objects DOM
4. Generate realDOM
nodes based on virtual objects
template parsing
Through the previous analysis, we know that in the template compilation stage const ast = parse(template.trim(), options)
, AST
the tree will be called to generate, and the processing v-model
of will focus processAttrs
on the function.
In the process processAttrs
of processing, the attribute processing of the template is divided into two parts, one part is html
the processing of ordinary tag attributes, and the other part is the processing of vue
the instruction . As for the processing of vue
the instruction , v-on
v-bind
special processing is carried out for , and other Vue
instructions will execute addDirective
the process for processing.
function processAttrs (el) {const list = el.attrsListlet i, l, name, rawName, value, modifiers, syncGen, isDynamicfor (i = 0, l = list.length; i < l; i++) {name = rawName = list[i].namevalue = list[i].valueif (dirRE.test(name)) {// 对 Vue 指令的处理// mark element as dynamicel.hasBindings = true// modifiersmodifiers = parseModifiers(name.replace(dirRE, ''))if (bindRE.test(name)) { // v-bind // v-bind 指令处理 过程} else if (onRE.test(name)) { // v-on// v-on 指令处理过程} else { // normal directives// 对于非 v-bind v-on 的 vue 指令处理过程name = name.replace(dirRE, '')// parse argconst argMatch = name.match(argRE)let arg = argMatch && argMatch[1]isDynamic = falseif (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 {// literal attribute// 普通 html 标签属性处理过程}}
}
In the process of analyzing the event mechanism, we know that the processing of the instructionVue
is to add attributes . Similarly, ordinary instructions will add attributes .v-on
AST
events
AST
directives
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
}
Finally AST
, there is an additional directives
attribute on the tree, as shown in the figure below, where modifiers
represents the modifier added in the template, such as.lazy
.number
Generation of the render function
render
The generation phase of the function, which is the previously analyzed genData
logic , genData
will process many attributes of the template and return the final spliced string template, and the processing of the instruction will enter genDirectives
the process
export function genData (el: ASTElement, state: CodegenState): string {let data = '{'const dirs = genDirectives(el, state)if (dirs) data += dirs + ','// ...
}
The logic of genDirectives is not complicated, by traversing the array of directives, and finally returning the directives:[
wrapped string
function genDirectives (el: ASTElement, state: CodegenState): string | void {const dirs = el.directivesif (!dirs) return// 字符串拼接let res = 'directives:['let hasRuntime = falselet i, l, dir, needRuntimefor (i = 0, l = dirs.length; i < l; i++) {dir = dirs[i]needRuntime = true// 对 AST 树重新处理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 = trueres += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''}${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''}${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''}},`}}if (hasRuntime) {return res.slice(0, -1) + ']'}
}
In genDirectives
the function , state.directives[dir.name]
the processing function of the corresponding instruction will be obtained through , and the processing function of these instructions has different implementations for different platforms. In the compilation process, the different compilation processes of different platforms are separated by means of partial functions, and the same configuration is provided for each platform each time for option merging and caching. For browsers, there are three important command options
var directives = {model: model$1,text: text,html: html
};
state.directives[dir.name]
And is the corresponding model
function, look at the logic of model
the function
function model$1 (el,dir,_warn
) {warn$2 = _warn;// 绑定的值var value = dir.value;var modifiers = dir.modifiers;var tag = el.tag;var type = el.attrsMap.type;{//如果 input 元素的 type 是 file , 如果还使用 v-model 进行双向绑定则会发出警告if (tag === 'input' && type === 'file') {warn$2("<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +"File inputs are read only. Use a v-on:change listener instead.",el.rawAttrsMap['v-model']);}}// 组件上的 v-modelif (el.component) {genComponentModel(el, value, modifiers);// component v-model doesn't need extra runtimereturn false} else if (tag === 'select') {// select 表单genSelect(el, value, modifiers);} else if (tag === 'input' && type === 'checkbox') {// checkboxgenCheckboxModel(el, value, modifiers);} else if (tag === 'input' && type === 'radio') {// radiogenRadioModel(el, value, modifiers);} else if (tag === 'input' || tag === 'textarea') {// 普通的 inputgenDefaultModel(el, value, modifiers);} else {// 如果不是以上几种类型,则默认为组件上的双向绑定genComponentModel(el, value, modifiers);// component v-model doesn't need extra runtimereturn false}// ensure runtime directive metadatareturn true
}
Obviously model
, AST
the tree will be further processed in the function, we know that there are different types of forms, and the event response mechanisms corresponding to different types are also different. Therefore, different render
functions . Here we focus on analyzing input
the processing of tags, which is getDefaultModel
the method .
function genDefaultModel (el,value,modifiers
) {var type = el.attrsMap.type;// 如果 v-bind 和 v-model 的值相同,则抛出错误{var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];if (value$1 && !typeBinding) {var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';warn$2(binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +'because the latter already expands to a value binding internally',el.rawAttrsMap[binding]);}}// 拿到 v-model 的修饰符var ref = modifiers || {};var lazy = ref.lazy;var number = ref.number;var trim = ref.trim;var needCompositionGuard = !lazy && type !== 'range';// lazy 修饰将触发同步的事件,从 input 改为 changevar event = lazy? 'change': type === 'range'? RANGE_TOKEN: 'input';var valueExpression = '$event.target.value';if (trim) {// 过滤输入的首尾空格valueExpression = "$event.target.value.trim()";}if (number) {// 将输入值转换成数字类型valueExpression = "_n(" + valueExpression + ")";}// 处理 v-model 的格式,允许使用如下格式 v-model=“a.b” v-mode="a[b]"var code = genAssignmentCode(value, valueExpression);if (needCompositionGuard) {// 确保不会在输入发组合文字过程中得到更新code = "if($event.target.composing)return;" + code;}// 添加 valueaddProp(el, 'value', ("(" + value + ")"));// 绑定事件addHandler(el, event, code, null, true);if (trim || number) {addHandler(el, 'blur', '$forceUpdate()');}
}
function genAssignmentCode (value,assignment
) {// 处理 v-model 的格式v-model="a.b"v-model="a[b]"var res = parseModel(value);if (res.key === null) {return (value + "=" + assignment)} else {return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")}
}
The logic of getDefaultModel is divided into two parts. One part is to generate different event processing strings for modifiers, but v-model
to AST
add attributes and event-related attributes to the generated tree. The key two lines of code are
// 添加 value
addProp(el, 'value', ("(" + value + ")"));
// 绑定事件
addHandler(el, event, code, null, true);
Going back genData
, after genDirectives
processing , the original AST
has added two new attributes, so it is also necessary to props
process events
the branches of and in the string processing process, and the final result of render
the function is
"_c('input',{directives:[{name:"model",rawName:"v-model",value:(message),expression:"message"}],attrs:{"type":"text"},domProps:{"value":(message)},on:{"input":function($event){if($event.target.composing)return;message=$event.target.value}}})"
Generate real DOM
DOM
It is necessary to generate virtual before generating real DOM
, and the process DOM
of is the same as before, there is no special place. DOM
After having the virtual , start to generate the real DOM
, that is patchVnode
, the key is createElm
the method the information related to the previous instruction will be stored in the attributevnode
of the , so the processing of the attribute will follow the logicdata
invokeCreateHooks
function createElm (vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index
) {// ....if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue)}
}
invokeCreateHooks
It will call the defined hook function vnode
to actually DOM
process the properties, instructions, events, etc. defined above, including the following steps (parts)
- 1. will
updateDOMProps
update the value of the tag with thevnode data
domProps
input
value
- 2. Will
updateAttrs
update the attribute value of the node using the attribute onvnode data
attrs
- 3. Use
updateDOMListeners
the property on to add event listenersvnode data
on
Therefore, the final result of v-model
syntactic sugar is input
to affect its own value
value .
Components use v-model
Using v-model on components is essentially syntactic sugar for parent-child component communication. Let's start with a simple example
var child = {template: '<div><input type="text" :value="value" @input="emitEvent">{
{value}}</div>',methods: {emitEvent(e) {this.$emit('input', e.target.value)}},props: ['value']}
new Vue({ data() { return { message: 'test' } }, components: { child }, template: '<div id="app"><child v-model="message"></child></div>', el: '#app'
})
Using v-model on the parent component, the child component will use the prop named value and the event named input by default. The difference between the AST generation phase and the ordinary form control is that when a child is encountered, it is not an ordinary html tag, it will The procedure of getComponentModel is executed, and the result of getComponentModel adds the model attribute on the AST tree.
export function genComponentModel (el: ASTElement,value: string,modifiers: ?ASTModifiers
): ?boolean {const { number, trim } = modifiers || {}const baseValueExpression = '$$v'let valueExpression = baseValueExpressionif (trim) {valueExpression =`(typeof ${baseValueExpression} === 'string'` +`? ${baseValueExpression}.trim()` +`: ${baseValueExpression})`}if (number) {valueExpression = `_n(${valueExpression})`}const assignment = genAssignmentCode(value, valueExpression)// 在 AST 树上添加 model 属性,其中有 value 、 expression 、 callback 属性el.model = {value: `(${value})`,expression: JSON.stringify(value),callback: `function (${baseValueExpression}) {${assignment}}`}
}
After processing the AST tree, return to the process of genData. Due to the addition of the model attribute, the string spliced by the parent component will be further processed.
function genData (el: ASTElement, state: CodegenState): string {const dirs = genDirectives(el, state)if (dirs) data += dirs + ','// ...// v-model 组件的 render 函数处理if (el.component) {data += `tag:"${el.tag}",`}
}
Therefore, the final render function of the parent component behaves as
"_c('child',{model:{value:(message),callback:function ($$v) {message=$$v},expression:"message"}})"
In the creation phase of subcomponents, Zhao Li will execute createComponent, and the logic of the model needs to be specially explained
function createComponent() {// transform component v-model data into props & eventsif (isDef(data.model)) {// 处理父组件的v-model指令对象transformModel(Ctor.options, data);}
}
function transformModel (options, data) {// prop默认取的是value,除非配置上有model的选项var prop = (options.model && options.model.prop) || 'value';// event默认取的是input,除非配置上有model的选项var event = (options.model && options.model.event) || 'input'// vnode上新增props的属性,值为value;(data.attrs || (data.attrs = {}))[prop] = data.model.value;// vnode上新增on属性,标记事件var on = data.on || (data.on = {});var existing = on[event];var 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;}
}
As can be seen from the logic of transformModel, the vnode of the subcomponent will add data.model.value to data.props, and add data.model.callback to data.on.
k].concat(existing);}} else {on[event] = callback;}
}
As can be seen from the logic of transformModel, the vnode of the subcomponent will add data.model.value to data.props, and add data.model.callback to data.on.
Obviously, this way of writing is the way of time communication, and this process goes back to the analysis process of event instructions. Using v-mode on components is essentially a syntactic sugar for parent-child component communication.
at last
Recently, I also sorted out a JavaScript and ES note, a total of 25 important knowledge points, and explained and analyzed each knowledge point. It can help you quickly master the relevant knowledge of JavaScript and ES, and improve work efficiency.
Friends in need, you can click the card below to receive and share for free