v-model
本质是 prop 和 事件监听 的语法糖。
通过 prop 实现
数据 -> 视图
的单向数据流;通过监听 change 或 input 事件实现视图 -> 数据
的单向数据流。
/*** v-model 的实现* 注册了 created 和 beforeUpdate 两个钩子函数*/
const VModelText = {/*** created 钩子函数* @param {Object} el - 节点对象* @param {Object} binding - binding 对象* @param {Object} vnode - 虚拟节点对象* created 主要做了 3 件事:* 1、将 js 对象的 value 赋值为元素的 value 属性* 2、通过 getModelAssigner 方法获取 props 中的 onUpdate:modelValue 属性赋值给元素的 _assign 属性* 3、通过 addEventListener 监听 input 标签的事件*/created(el, { value, modifiers: { lazy, trim, number } }, vnode) {// 1、将 js 对象的 value 赋值为元素的 value 属性(数据 -> 视图的单向数据流)el.value = value == null ? '' : value// 2、通过 getModelAssigner 方法获取 props 中的 onUpdate:modelValue 属性赋值给元素的 _assign 属性el._assign = getModelAssigner(vnode)// 判断是否配置了 number 或 元素的 type 为 numberconst castToNumber = number || el.type === 'number'// 3、通过 addEventListener 监听 input 标签的事件// 根据 lazy 决定监听元素的 change 或 input 事件(change | input)addEventListener(el, lazy ? 'change' : 'input', e => {if (e.target.composing) return// 获取元素的新值let domValue = el.value// 如果配置了 trim,则调用 String.trim() 清除首尾空格if (trim) {domValue = domValue.trim()}// 如果配置了 number 或元素的 type 为 number,则转换成 number 后再赋值给元素else if (castToNumber) {domValue = toNumber(domValue)}// 更新数据el._assign(domValue)})// 如果配置了 trim,则调用 String.trim() 清除首尾空格if (trim) {addEventListener(el, 'change', () => {el.value = el.value.trim()})}// 如果没有配置 lazy,则添加两个事件对中文输入法输入进行监听if (!lazy) {addEventListener(el, 'compositionstart', onCompositionStart)addEventListener(el, 'compositionend', onCompositionEnd)}},/*** beforeUpdata 钩子函数* @param {Object} el - 节点对象* @param {Object} binding - binding 对象* @param {Object} vnode - 虚拟节点对象*/beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {el._assign = getModelAssigner(vnode)if (document.activeElement === el) {// 如果配置了 trim,则将元素的值通过 String.trim() 清除首尾空格和再赋值给数据if (trim && el.value.trim() === value) return// 如果配置了 number 或 元素的 type 为 number,则将元素的值转换为 number 再赋值给数据if ((number || el.type === 'number') && toNumber(el.value) === value) return}const newValue = value == null ? '' : value// 更新前判断新旧值是否相同,如果不同,则把数据更新到 DOM 上if (el.value !== newValue) {el.value = newValue}}
}/*** 获取 model 分配器*/
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.targetif (target.composing) {target.composing = false// 手动触发 input 事件进行赋值trigger(target, 'input')}
}