双向数据绑定这个概念或者大家并不陌生,视图影响数据,数据同样影响视图,两者间有双向依赖的关系。在响应式系统构建的上,中,下篇我已经对数据影响视图的原理详细阐述清楚了。而如何完成视图影响数据这一关联?这就是本节讨论的重点:指令
v-model
。
由于v-model
和前面介绍的插槽,事件一致,都属于vue提供的指令,所以我们对v-model
的分析方式和以往大同小异。分析会围绕模板的编译,render
函数的生成,到最后真实节点的挂载顺序执行。最终我们依然会得到一个结论,v-model无论什么使用场景,本质上都是一个语法糖。
11.1 表单绑定
11.1.1 基础使用
v-model
和表单脱离不了关系,之所以视图能影响数据,本质上这个视图需要可交互的,因此表单是实现这一交互的前提。表单的使用以<input > <textarea> <select>
为核心,更细的划分结合v-model
的使用如下:
// 普通输入框
<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>
// 原生单选框的写法 注:原生单选框的写法需要通过name绑定一组单选,两个radio的name属性相同,才能表现为互斥
<div class="group">
<input type="radio" name="number" value="one">one
<input type="radio" name="number" value="two">two
</div>
// 多选框 (原始值: value4: [])
<div class="group">
<input type="checkbox" value="jack" v-model="value4">jack
<input type="checkbox" value="lili" v-model="value4">lili
</div>
// 下拉选项
<select name="" id="" v-model="value5">
<option value="apple">apple</option>
<option value="banana">banana</option>
<option value="bear">bear</option>
</select>
接下来的分析,我们以普通输入框为例
<div id="app">
<input type="text" v-model="value1">
</div>
new Vue({
el: '#app',
data() {
return {
value1: ''
}
}
})
进入正文前先回顾一下模板到真实节点的过程。
-
- 模板解析成
AST
树;
- 模板解析成
-
AST
树生成可执行的render
函数;
-
render
函数转换为Vnode
对象;
-
- 根据
Vnode
对象生成真实的Dom
节点。
- 根据
接下来,我们先看看模板解析为AST
树的过程。
11.1.2 AST树的解析
模板的编译阶段,会调用var ast = parse(template.trim(), options)
生成AST
树,parse
函数的其他细节这里不展开分析,前面的文章或多或少都涉及过,我们还是把关注点放在模板属性上的解析,也就是processAttrs
函数上。
使用过vue
写模板的都知道,vue
模板属性由两部分组成,一部分是指令,另一部分是普通html
标签属性。z这也是属性处理的两大分支。而在指令的细分领域,又将v-on,v-bind
做特殊的处理,其他的普通分支会执行addDirective
过程。
// 处理模板属性
function processAttrs(el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name; // v-on:click
value = list[i].value; // doThis
if (dirRE.test(name)) {
// 1.针对指令的属性处理
···
if (bindRE.test(name)) {
// v-bind分支
···
} else if(onRE.test(name)) {
// v-on分支
···
} else {
// 除了v-bind,v-on之外的普通指令
···
// 普通指令会在AST树上添加directives属性
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
if (name === 'model') {
checkForAliasModel(el, value);
}
}
} else {
// 2. 普通html标签属性
}
}
}
在深入剖析Vue源码 - 揭秘Vue的事件机制这一节,我们介绍了AST
产生阶段对事件指令v-on
的处理是为AST
树添加events
属性。类似的,普通指令会在AST
树上添加directives
属性,具体看addDirective
函数。
// 添加directives属性
function addDirective (el,name,rawName,value,arg,isDynamicArg,modifiers,range) {
(el.directives || (el.directives = [])).push(rangeSetItem({
name: name,
rawName: rawName,
value: value,
arg: arg,
isDynamicArg: isDynamicArg,
modifiers: modifiers
}, range));
el.plain = false;
}
参考