前言
今天,vue的v-model在不同标签上,编译的时候做了不一样的处理,现在来一起了解一下把
v-model的编译处理
- 定义在组件上
- 定义在input下拉选择上
- 定义在input多选上
- 定义在input单选上
- 定义在input多行文本输入框上
export default function model (
el: ASTElement,
dir: ASTDirective,
_warn: Function
): ?boolean {
const value = dir.value
const modifiers = dir.modifiers
const tag = el.tag
const type = el.attrsMap.type
if (el.component) {
genComponentModel(el, value, modifiers)
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)
return false
}
return true
}
v-model在组件上
- 根据v-model的修饰符是number/trim,将字符串案规则处理
- 生成callback回调函数注册成自定义事件,将value=v-model值
- 绑定input自定义事件为callback,触发时给v-model重新赋值
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})`
}
// 将表达式赋值给value
const assignment = genAssignmentCode(value, valueExpression)
el.model = {
value: `(${value})`,
expression: JSON.stringify(value),
callback: `function (${baseValueExpression}) {${assignment}}`
}
}
// 编辑完生成的代码
with(this) {
return _c('haha', {
model: {
value: (test),
callback: function($$v) {
test = $$v
},
expression: "test"
}
})
}
// 创建组件时,对model进行处理
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.value
const 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
}
}
v-model在select标签上
- 将下拉组件中选中的选项的值处理成数组
- 注册change事件,下拉更改后重新给value赋值
function genSelect (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
const number = modifiers && modifiers.number
// 将选中的下拉处理成数组
const selectedVal = `Array.prototype.filter` +
`.call($event.target.options,function(o){return o.selected})` +
`.map(function(o){var val = "_value" in o ? o._value : o.value;` +
`return ${number ? '_n(val)' : 'val'}})`
const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
let code = `var $$selectedVal = ${selectedVal};`
code = `${code} ${genAssignmentCode(value, assignment)}`
addHandler(el, 'change', code, null, true)
}
// 编译后的代码
with(this) {
return _c('select', {
directives: [{
name: "model",
rawName: "v-model",
value: (test),
expression: "test"
}],
on: {
"change": function($event) {
var $$selectedVal = Array.prototype.filter.call($event.target.options, function(o) {
return o.selected
})
.map(function(o) {
var val = "_value" in o ? o._value : o.value;
return val
});
test = $event.target.multiple ? $$selectedVal : $$selectedVal[0]
}
}
})
}
v-model在input的多选框上
- 获取value、true-value、false-value的值
- 根据v-model是否是数组,input框的value的值,判断多选框是否选中
- 绑定change事件,触发了对v-model重新赋值
function genCheckboxModel (
el: ASTElement,
value: string, // v-model的值
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})` // 直接返回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
)
}
// 编译后的代码
with(this) {
return _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (test),
expression: "test"
}],
attrs: {
"type": "checkbox"
},
domProps: {
"checked": Array.isArray(test) ? _i(test, null) > -1 : (test)
},
on: {
"change": function($event) {
var $$a = test,
$$el = $event.target,
$$c = $$el.checked ? (true) : (false);
if (Array.isArray($$a)) {
var $$v = null,
$$i = _i($$a, $$v); // $$v在数组中的索引
// 选中的话,数组中能含该项;没选中,不能有
if ($$el.checked) {
$$i < 0 && (test = $$a.concat([$$v]))
} else {
$$i > -1 && (test = $$a.slice(0, $$i)
.concat($$a.slice($$i + 1)))
}
} else {
// 不是数组,直接赋值
test = $$c
}
}
}
})
}
v-model在input的单选框上
- 直接取出输入框的value的值
- change事件时,赋值给v-model
function genRadioModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
const number = modifiers && modifiers.number
let valueBinding = getBindingAttr(el, 'value') || 'null'
valueBinding = number ? `_n(${valueBinding})` : valueBinding
addProp(el, 'checked', `_q(${value},${valueBinding})`)
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
}
// 编译后的代码
with(this) {
return _c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (test),
expression: "test"
}],
attrs: {
"type": "radio"
},
domProps: {
"checked": _q(test, null)
},
on: {
"change": function($event) {
test = null
}
}
})
}
标签是input或者textarea
function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
): ?boolean {
const type = el.attrsMap.type
const { lazy, number, trim } = modifiers || {}
const needCompositionGuard = !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()')
}
}
最后
v-model的在input的type属性不一样时,处理的方式不一样,尤其在type=checkbox时,可以支持值是一个数组,这个功能一直以为是element-ui处理,其实就是vue做的处理。所以,多看源码才能更加了解vue的使用的细节。欢迎大家点赞收藏~