大家好,我是前端小喵
带着问题看源码
1、input在IME(中文,日文和韩文等) 拼字阶段输入阶段,为什么input事件监听不到(原生事件是可以监听到的),是v-model源码做了内部处理吗
2、v-model.lazy,v-model.trim,v-model.number分别是怎么处理的
一、vModelText源码
看到这一坨难免有点懵,其实可以拆几个模块去理解这段代码
export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
const castToNumber =
number || (vnode.props && vnode.props.type === 'number')
addEventListener(el, lazy ? 'change' : 'input', e => {
if ((e.target as any).composing) return
let domValue: string | number = el.value
if (trim) {
domValue = domValue.trim()
}
if (castToNumber) {
domValue = looseToNumber(domValue)
}
el._assign(domValue)
})
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
}
if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart)
addEventListener(el, 'compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
addEventListener(el, 'change', onCompositionEnd)
}
},
// set value on mounted so it's after min/max for type="range"
mounted(el, { value }) {
el.value = value == null ? '' : value
},
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
// avoid clearing unresolved text. #2302
if ((el as any).composing) return
if (document.activeElement === el && el.type !== 'range') {
if (lazy) {
return
}
if (trim && el.value.trim() === value) {
return
}
if (
(number || el.type === 'number') &&
looseToNumber(el.value) === value
) {
return
}
}
const newValue = value == null ? '' : value
if (el.value !== newValue) {
el.value = newValue
}
}
}
二、代码分析
2.1、created:对数据变化做事件监听
1、保存获取更新数值方法(保证传入更新值,无论是一个v-model绑定还是多个v-model绑定,都会更新所有v-model绑定的值)
el._assign = getModelAssigner(vnode)
// 可以跳过:getModelAssigner内部逻辑
var getModelAssigner = (vnode) => {
// 更新v-model值方法
const fn = vnode.props["onUpdate:modelValue"] || false;
// 默认返回更新数值方法fn
// 多个v-model绑定的时候,fn为数组,返回函数
入参:当前值 遍历fn去更新所有数值
return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;
};
var invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
2、保存变量castToNumber是否要做number处理
// 如果v-model修饰符为numer v-model.number 或者 虚拟dom vnode的props.type 为 'number'(例如 input 的type为number情况)
const castToNumber = number || vnode.props && vnode.props.type === "number";
3、监听数据变化
// 根据是否有修饰符 lazy 决定监听change还是input
addEventListener(el, lazy ? "change" : "input", (e) => {
// IME(中文,日文和韩文等) 拼字阶段加锁(下面的 !lazy处理的compositionstart、compositionend)
// 在!lazy的compositionstart、compositionend之间 不断触发input事件,被锁住 return出去不做更新值处理
if (e.target.composing) return;
let domValue = el.value;
// trim处理
if (trim) {
domValue = domValue.trim();
}
// number处理:
// looseToNumber工具函数 数字格式化处理
if (castToNumber) {
domValue = looseToNumber(domValue);
}
// 一开始保存的更新值方法 直接更新数值
el._assign(domValue);
});
// trim 就监听change 对最后值做trim处理
if (trim) {
addEventListener(el, "change", () => {
el.value = el.value.trim();
});
}
// !lazy的时候 监听(compositionstart、compositionend、change事件)
if (!lazy) {
// compositionstart比input更先执行 只执行一次 IME拼字开始时 加锁
addEventListener(el, "compositionstart", onCompositionStart);
// compositionend比input更先执行 只执行一次 IME拼字结束 解锁 触发input事件
addEventListener(el, "compositionend", onCompositionEnd);
// 监听change 调用 解锁 触发input事件
addEventListener(el, "change", onCompositionEnd);
}
// 可以跳过:looseToNumber函数、onCompositionStart、onCompositionEnd
// parseFloat处理,值为NaN直接返回val 否则返回parseFloat处理的值
var looseToNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
// onCompositionStart 加锁
function onCompositionStart(e) {
e.target.composing = true;
}
// onCompositionEnd 解锁 触发input事件
function onCompositionEnd(e) {
const target = e.target;
if (target.composing) {
target.composing = false;
target.dispatchEvent(new Event("input"));
}
}
2.2、mounted:赋值到dom的value属性上
mounted(el, { value }) {
// 赋值dom 值为null 则赋值dom为 ''
el.value = value == null ? "" : value;
},
2.3、beforeUpdate: 更新值前的各种校验
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
// 更新值方法
el._assign = getModelAssigner(vnode);
// IME(中文,日文和韩文等) 拼字阶段 return
if (el.composing)
return;
// 当前获得焦点的元素为当前dom 并且dom type不为range
if (document.activeElement === el && el.type !== "range") {
// lazy 监听的change事件 return
if (lazy) {
return;
}
// trim前后值相等
if (trim && el.value.trim() === value) {
return;
}
// number处理前后值相等
if ((number || el.type === "number") && looseToNumber(el.value) === value) {
return;
}
}
// 值为null 置空字符串
const newValue = value == null ? "" : value;
// 前后值不相等就更新
if (el.value !== newValue) {
el.value = newValue;
}
}
三、总结
1、created: 对lazy、非lazy、trim不同情况,做对应的事件监听;非lazy有IME拼字阶段优化、trim、number有相对应的数据处理
2、mounted:赋值到dom的value属性上
3、更新值前的各种校验: 是否在拼字阶段; 当前获得焦点的元素为当前dom 并且dom type不为range前提下,修饰符是否为lazy、trim前后值相等、number处理前后值相等;值为null 置空字符串,值前后是否相等