v-model 也不是可以作用到任意标签,它只能在一些特定的表单标签如 input、select、textarea 和自定义组件中使用。
在普通表单元素上作用v-model
基本示例
<input v-model="searchText">
模板编译后生成的render函数
import { vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return _withDirectives((_openBlock(), _createBlock("input", {
"onUpdate:modelValue": $event => (_ctx.searchText = $event)
}, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
[_vModelText, _ctx.searchText]
])
}
作用在 input 标签的 v-model 指令在编译后,除了使用 withDirectives 给这个 vnode 添加了 vModelText 指令对象外,还额外传递了一个名为 onUpdate:modelValue 的 prop,它的值是一个函数,这个函数就是用来更新变量 searchText。
const vModelText = {
created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el.value = value == null ? '' : value //把v-model绑定的value值复制给el.value;数据到DOM的数据流
el._assign = getModelAssigner(vnode) //获取props中的onUpdate:modelValue属性对应的函数
const castToNumber = number || el.type === 'number'
addEventListener(el, lazy ? 'change' : 'input', e => {//如果有lazy修饰符,添加change事件,没有的话绑定事件
if (e.target.composing) //composing代表的是:是否是处于中文输入法
return
let domValue = el.value
if (trim) { //是否有trim修饰符
domValue = domValue.trim()
}
else if (castToNumber) {//是否有number修饰符
domValue = toNumber(domValue)
}
el._assign(domValue) //把dom上的新的value值复制给v-model的变量。DOM到数据的流动。
})
if (trim) {//change事件是input输入框失去焦点,且值变化的时候触发
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
}
if (!lazy) {//如果使用的是中文输入法,出触发compositionstart,选中某文字之后触发compositionend
addEventListener(el, 'compositionstart', onCompositionStart)//中文输入数输入过程中不触发input事件
addEventListener(el, 'compositionend', onCompositionEnd)//中文输入完之后手动触发input事件
}
},
beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {//当组件更新时
el._assign = getModelAssigner(vnode)
if (document.activeElement === el) {
if (trim && el.value.trim() === value) {//如果传进来value值和el.value没有变化,直接返回
return
}
if ((number || el.type === 'number') && toNumber(el.value) === value) {
return
}
}
const newValue = value == null ? '' : value
if (el.value !== newValue) {//传进来的新值替换掉el.value
el.value = newValue
}
}
}
const getModelAssigner = (vnode) => {
const fn = vnode.props['onUpdate:modelValue']
return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
}
function onCompositionStart(e) {
e.target.composing = true
}
function onCompositionEnd(e) {
const target = e.target
if (target.composing) {
target.composing = false
trigger(target, 'input')
}
}
vModelText指令实现了两个钩子函数:created和beforeUpdate
注意钩子函数的生命周期也是变了的
created 部分:第一个参数 el 是节点的 DOM 对象,第二个参数是 binding 对象,第三个参数 vnode 是节点的 vnode 对象。
1,created过程
传进来的value赋值给el.value
获取update:modelValue的事件处理函数
el的事件绑定
lazy是false是再绑定compositionstart和compositionend事件,特殊处理输入法
2,beforeUpdate组件更新
判断值有变化就把新值value赋值给el.value
在自定义组件上定义v-model
我们来看一下这个模板编译后生成的 render 函数:import { resolveComponent as _resolveComponent, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_custom_input = _resolveComponent("custom-input")
return (_openBlock(), _createBlock(_component_custom_input, {
modelValue: _ctx.searchText,
"onUpdate:modelValue": $event => (_ctx.searchText = $event)
}, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))
}
可以看到,编译的结果似乎和指令没有什么关系,并没有调用 withDirective 函数。
<custom-input :modelValue="searchText" @update:modelValue="$event=>{searchText = $event}"/>
改写之后得到的编译的render函数是一样的。
v-model 作用于组件上本质就是一个语法糖,就是往组件传入了一个名为 modelValue 的 prop,它的值是往组件传入的数据 data,另外它还在组件上监听了一个名为 update:modelValue 的自定义事件,事件的回调函数接受一个参数,执行的时候会把参数 $event 赋值给数据 data。
Vue.js 3.0 给组件的 v-model 提供了参数的方式,允许我们指定 prop 的名称:
<custom-input v-model:text="searchText"/>。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
vue3区别于vue2
prop名称的变化:modelValue-->value
事件名的变化update:modelValue 的事件-->input事件
vue3可以直接修改prop名,vue2可以通过model配置或者通过.sync。但是vue3中.sync已经废弃了。