vue-model

表单v-model

  • parse的时候添加属性, generate的时候处理属性
  • processAttrs调用addDirective往el.directives添加指令信息

    parse 阶段

  • v-model 被当做普通的指令解析到 el.directives 中,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
    value = list[i].value;
    if (dirRE.test(name)) {
    name = name.replace(dirRE, '');
    addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
    } else {// 文本
    }
    }
    }
    function addDirective (
    // dom, name, 属性值
    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;
    }

generate 阶段

  • generate->genElement->genData$2->genDirectives , 添加回调对应的函数字符串
  • const dirs = genDirectives(el, state)
  • state var state = new CodegenState(options);, options在createCompiler中合并baseOption得来
  • 主要是通过添加以下两个属性实现v-model语法糖

    1
    2
    addProp(el, 'value', ("(" + value + ")")); // el上添加props
    addHandler(el, event, code, null, true); //
  • v-model处理了composition, 输入法完成输入前不修改model的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "directives": [
    {
    "name": "model",
    "rawName": "v-model",
    "value": message,
    "expression": "message"
    }
    ],
    "attrs": { "placeholder": "edit me" },
    "domProps": { "value": message },
    "on": {
    "input": function($event) {
    if ($event.target.composing) return;
    message = $event.target.value;
    }
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// model指令方法来源
var CodegenState = function CodegenState (options) {
this.options = options;
this.transforms = pluckModuleFunction(options.modules, 'transformCode');
this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
this.directives = extend(extend({}, baseDirectives), options.directives);
this.staticRenderFns = [];
};
function generate ( ast, options) {
var state = new CodegenState(options);
var code = ast ? genElement(ast, state) : '_c("div")';
return {
render: ("with(this){return " + code + "}"),
staticRenderFns: state.staticRenderFns
}
}
function genElement (el, state) {
// genStatic genOnce genFor genIf genChildren
// component or element
var code;
if (el.component) {
code = genComponent(el.component, el, state);
} else {
var data;
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData$2(el, state);
}
var children = el.inlineTemplate ? null : genChildren(el, state, true);
code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
// 静态属性css/style处理 module transforms
return code
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 可能对属性修改,优先其他属性处理
function genDirectives (el, state) {
var dirs = el.directives;
if (!dirs) { return }
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
var gen = 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:" + (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) + ']'
}
}
// 形如"directives:[{name:"model",rawName:"v-model",value:(message),expression:"message"}]"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function model (
el,
dir,
_warn
) {
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
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); // input属性
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers); // 属性v-node
// component v-model doesn't need extra runtime
return false
}
// ensure runtime directive metadata
return true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function genDefaultModel (
el,
value,
modifiers
) {
var type = el.attrsMap.type;
var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range'; // v-model处理了composition, 输入法完成输入前不修改model的值, 也可以通过配置lazy实现
var 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 + ")";
}
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
code = "if($event.target.composing)return;" + code;
}
// code如 "if($event.target.composing)return;message=$event.target.value"
addProp(el, 'value', ("(" + value + ")")); // el上添加props
addHandler(el, event, code, null, true); //
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}

运行时指令机制对composition处理

  • createRenderFunction时会加指令模板的hook, 这些hook会在patch的时候执行
  • 指令module的update hook会查找指令的定义function, 然后通过callHook$1执行对应的钩子(create只是添加 insert到vnode的hook里, insert执行要等到patch结束之前)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
function createElm () {
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue); // call cbs.create[i$1](emptyNode, vnode);
}
insert(parentElm, vnode.elm, refElm);
}
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable 普通节点没有hook, 通过底下的`mergeVNodeHook`后就有了
if (isDef(i)) {
if (isDef(i.create)) { i.create(emptyNode, vnode); }
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
// cbs.create,cbs.update执行以下方法
var directives = {
create: updateDirectives,
update: updateDirectives,
}
function updateDirectives (oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
// 指令的create, update都执行以下代码, 这个方法调用指令的bind/UNbind, 以及添加指令的insert,componentUpdated方法到vnode的insert和postpatch hook上去.
function _update (oldVnode, vnode) {
var isCreate = oldVnode === emptyNode;
var isDestroy = vnode === emptyNode;
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context); // 找到指令定义,
var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
var dirsWithInsert = [];
var dirsWithPostpatch = [];
var key, oldDir, dir;
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
if (!oldDir) {
// new directive, bind
callHook$1(dir, 'bind', vnode, oldVnode);
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
callHook$1(dir, 'update', vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
if (dirsWithInsert.length) {
var callInsert = function () {
for (var i = 0; i < dirsWithInsert.length; i++) {
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert);
} else {
callInsert();
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', function () {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// model指令的insert会绑定composition相关事件, 与genCode的`if($event.target.composing)return`相呼应
var directive = {
inserted: function inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903 bug
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers;
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart);
el.addEventListener('compositionend', onCompositionEnd);
el.addEventListener('change', onCompositionEnd);
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true;
}
}
}
},
componentUpdated: function componentUpdated (el, binding, vnode) {
if (vnode.tag === 'select') {
}
}
};

组件v-model

  • parse 阶段和表单v-model一样

generate 阶段

  • runtime不再需要, 添加了model而不是directive字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function model () {
if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers); // 属性v-node
// component v-model doesn't need extra runtime
return false
}
// ensure runtime directive metadata
return true
}
function genComponentModel (
el,
value,
modifiers
) {
var baseValueExpression = '$$v';
var valueExpression = baseValueExpression;
// trim number
var assignment = genAssignmentCode(value, valueExpression);
el.model = {
value: ("(" + value + ")"),
expression: JSON.stringify(value),
callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
};
// "{"value":"(message)","expression":"\"message\"","callback":"function ($$v) {message=$$v}"}"
}
function genAssignmentCode (
value,
assignment
) {
var res = parseModel(value);
if (res.key === null) {
return (value + "=" + assignment)
} else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
function genData$2 (el, state) {
var data = '{';
// directives first.
// directives may mutate the el's other properties before they are generated.
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
// component v-model
if (el.model) {
data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
}
return data
}

运行时生成语法糖

  • 没有涉及model指令, 就是往组件上增加props,on属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function createComponent () {
    // transform component v-model data into props & events
    if (isDef(data.model)) {
    transformModel(Ctor.options, data);
    }
    }
    // transform component v-model info (value and callback) into
    // prop and event handler respectively.
    function transformModel (options, data) {
    var prop = (options.model && options.model.prop) || 'value'; // 可在子组件中定义事件名和props属性名
    var event = (options.model && options.model.event) || 'input'
    ;(data.attrs || (data.attrs = {}))[prop] = data.model.value;
    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;
    }
    }