value+input方法的语法糖
可绑定:input,checkbox,select,textarea,radio
v-model一种是在表单元素上使用,另外一种是在组件上使用
此默认绑定input,v-model绑定值为value,默认在注释中逐行解释
引入:在模板的编译阶段, v-model跟其他指令一样,会被解析到 el.directives 中,之后会通过genDirectives方法处理这些指令
genDirectives
src/compiler/codegen/index.js
function genDirectives (el, state) {
// 指令参数,directives中有个model,然后赋值给dirs
var dirs = el.directives;
if (!dirs) { return }
var res = 'directives:[';
var hasRuntime = false;
var i, l, dir, needRuntime;
// dirs是个数组,genDirectives方法中遍历处理这些指令
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i];
needRuntime = true;
// 这里等于var gen = state.directives[model];
var gen = state.directives[dir.name];
if (gen) {
// 执行model()
needRuntime = !!gen(el, dir, state.warn);
}
}
执行model()
model
function model (el,dir,_warn) {
// value:model值
// tag:model,在input中使用就是input
// type: 绑定类型
var value = dir.value;
var modifiers = dir.modifiers;
var tag = el.tag;
var type = el.attrsMap.type;
// 判断绑定的什么,通过对应的类型判断进入不同的执行方法
// 这里会进入到genDefaultModel执行方法中
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
} 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
}
进入到genDefaultModel执行方法
genDefaultModel
// input --- 执行genDefaultModel方法
function genDefaultModel (el,value,modifiers) {
var type = el.attrsMap.type;
{
// 是否同时具有指令v-model和v-bind
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]
);
}
}
// 获取修饰符lazy, number及trim
var ref = modifiers || {};
var lazy = ref.lazy;
var number = ref.number;
var trim = ref.trim;
var needCompositionGuard = !lazy && type !== 'range';
// .lazy 取代input监听change事件
var event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input';
var valueExpression = '$event.target.value';
// .trim 输入首尾空格过滤
if (trim) {
valueExpression = "$event.target.value.trim()";
}
// .number 输入字符串转为数字
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}
// 接下来
}
接下来
//获取code
var code = genAssignmentCode(value, valueExpression);
if (needCompositionGuard) {
// code = if($event.target.composing)return; value=$event.target.value
// if($event.target.composing)return;不记录用户未确定的输入
// 用户未确定的输入
// value=$event.target.value在genAssignmentCode方法中返回
code = "if($event.target.composing)return;" + code;
}
// 绑定value
addProp(el, 'value', ("(" + value + ")"));
// 给el添加input事件处理
addHandler(el, event, code, null, true);
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()');
}
if($ event.target.composing)return;
不记录用户未确定的输入
value=$event.target.value
在genAssignmentCode方法中返回
用户未确定的输入:
aaaaa就是已确定输入,bbbbb就是未确定输入
genAssignmentCode
// 生成model中绑定的value值,返回code
function genAssignmentCode (
value,
assignment
) {
var res = parseModel(value);
if (res.key === null) {
// 返回value=$event.target.value
return (value + "=" + assignment)
} else {
// 但我们不走这里,当obj.value时会进入这里
// $set(obj, "value", $event.target.value)”
return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
}
}
$set
// value属性是否一开始就在obj中
// 如果存在就只是进行单纯的赋值
// 不存在的话在进行响应式操作
function set (target, key, val) {
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
// defineReactive为value绑定getter()和setter()
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val
}
addProp
function addProp (el, name, value, range, dynamic) {
// 首先判断el上有没有props
// 如果没有的话创建props并赋值为一个空数组
// 然后拼接对象并加入到props中
(el.props || (el.props = [])).push(rangeSetItem({ name: name, value: value, dynamic: dynamic }, range));
el.plain = false;
}
addProp的作用是让input动态绑定value
让原本的变成
然后继续执行addHandler
addHandler的作用是让input动态绑定input
让原本的变成
当有一个自定义的组件时,子组件通过this.$emit(‘input’,value)来对父组件传值,父组件接受到之后让e.target.value赋值给input中的value从而实现组件内部暴露出组件的值到 v-model所绑定的值中去
原生控件绑定事件,捕捉到原生组件的值,利用 $emit方法,触发input方法,组件监听到 input事件然后把值传入到value中
自定义model时,只需要在组件中传入model的prop和event属性
createComponent
src/core/vdom/create-component.js
// 如果当前数据有model属性,就会使用transformModel转换model
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
transformModel
src/core/vdom/create-component.js
function transformModel (options, data: any) {
// 如果没有prop属性默认就是value
const prop = (options.model && options.model.prop) || 'value'
// 如果没有event属性,默认就是input
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value// data.attrs.value="xxx"
const on = data.on || (data.on = {})
const existing = on[event] // 给on绑定input的事件,对应的函数就是callback
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
}
}
自定义model:
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('' );
// 解析后
with(this) {
return _c('el-checkbox', {
model: { // v-model解析后
value: (check),
callback: function ($$v) {
check = $$v
},
expression: "check"
}
})
}
传入prop和event后对应的转换
Vue.component('el-checkbox', {
template:``,
model:{
prop:'check', // 更改默认的value的名字
event:'change' // 更改默认的方法名
},
props: {
check: Boolean
}
})