Vue v-model 指令详解
Vue 提供了v-model 指令来帮助用户方便的处理表单元素的数据绑定和用户输入内容同步到数据上(双向绑定)。
用法如下:
初始test的值会绑定到input的value中
当用户修改input的内容
test的变量也同时更新。
"_c('input',{directives:[{name:"model",rawName:"v-model",value:(test),expression:"test"}]
,attrs:{"type":"text"},domProps:{"value":(test)},
on:{"input":function($event){if($event.target.composing)return;
test=$event.target.value}}})"
上面是vue 为v-model生成的render 代码,我们可以看到v-model 最终除了绑定默认的值外,还添加了一个input事件,input事件将input的值再重新传给绑定的值。所以v-model其实是一个双向绑定的语法糖
转换方法
function genDefaultModel (
el,
value,
modifiers
) {
var type = el.attrsMap.type;
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
{
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$1(
binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
);
}
}var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
// lazy 修饰符会强制将event改为change
var event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input';
var valueExpression = '$event.target.value';
if (trim) {
// trim修饰符会处理值的前后空格
valueExpression = "$event.target.value.trim()";
}
if (number) {
// number修饰符会尝试将数字进行number转换,失败了还保留原值
valueExpression = "_n(" + valueExpression + ")";
}var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
//生成input事件代码
code = "if($event.target.composing)return;
" + code;
}addProp(el, 'value', ("(" + value + ")"));
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
}function genAssignmentCode (
value,
assignment
) {
var res = parseModel(value);
if (res.key === null) {
return (value + "=" + assignment)
} else {
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
如果我们给一个自定义组件添加v-model
因为v-model默认是需要绑定在具有input或者change事件的表单元素上的。所以对于非表单元素比如封装了表单元素的自定义组件,v-model是需要我们自己来实现这个语法糖的事件传递的,具体做法参考下面代码:
Vue.component('base-input', {
props: {
‘value’: String
},
template: `
`
})
组件v-model生成的中间代码
"_c('div',{attrs:{"id":"app"}},
[_c('test',{model:{value:(test),
callback:function ($$v) {
test=$$v
},expression:"test"}})],1)"
文章图片
最终model里的callback会转化为组件vnode里的 on事件map里了。
转换方法:
function transformModel (options, data) {
var prop = (options.model && options.model.prop) || 'value';
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 {
// callback 传入到on的map里了
on[event] = callback;
}
}
所以在组件内部我们要将form元素的 input事件(或者radio、checkbox的change事件)通过$emit的方法传出来。外面的组件上vue已经帮我们默认添加好了input事件来接受参数。
v-model 完整的流程代码:
1.处理v-model指令生成代码
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;
// 这里取model的gen方法 处理v-model此时 dir.name = "model"
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) + ']'
}
}
【Vue v-model 指令详解】不同的form表单元素绑定会有一些处理细节上的差别)
function model (
el,
dir,
_warn
) {
debugger
warn$1 = _warn;
var value = https://www.it610.com/article/dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
{
// inputs with type="file" are read only and setting the input's
// value will throw an error.
if (tag === 'input' && type === 'file') {
warn$1(
"<" + (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-model的处理
if (el.component) {
genComponentModel(el, value, modifiers);
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') { // select 标签的处理
genSelect(el, value, modifiers);
} else if (tag === 'input' && type === 'checkbox') { // input type为checkbox的处理
genCheckboxModel(el, value, modifiers);
} else if (tag === 'input' && type === 'radio') {// input type为radio的处理
genRadioModel(el, value, modifiers);
} else if (tag === 'input' || tag === 'textarea') {// input type为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 {
warn$1(
"<" + (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
}
推荐阅读
- vue-cli|vue-cli 3.x vue.config.js 配置
- 2020-04-07vue中Axios的封装和API接口的管理
- VueX--VUE核心插件
- 动态组件与v-once指令
- vue组件中为何data必须是一个函数()
- 用npm发布一个包的教程并编写一个vue的插件发布
- vuex|vuex 基础结构
- Vue源码分析—响应式原理(二)
- VueX(Vuex|VueX(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式)
- vue中的条件判断详解v-if|vue中的条件判断详解v-if v-else v-else-if v-show