v-model原理分析

以前就因为v-model踩过一次坑,没想到前几天再次踩坑。问题如下:

<input v-model="loginForm.phoneNumber" @input="limit" type="text">
limit(e) {
      e.target.value = e.target.value.replace(/[^\d]/g,'')
      console.log('value值-----'+e.target.value)
      console.log('model值-----'+this.loginForm.phoneNumber)
    }

使用了一个函数限制输入框只能输入数字,输入其他将会被置空,在输入1时是正确的,之后我们输入一个非数字,这个非数字并被置空之后,再输入新的数字,出现了下面这样的情况:

v-model的值和value的值不同,v-model无法获取到新输入的数值。

 

为什么会这样呢?百度半天无果,只好研究一波vue源码。v-model获取值是监听输入框的input事件,触发genDefaultModel 方法,如下便是vue中实现v-model绑定的方法,可以发现,在方法中有一句判断:code = `if($event.target.composing)return;${code}` ,这是什么意思呢?经查找MDN得知composing用来判断input事件是否由IME (即由文字输入法触发)构成触发的。

function genDefaultModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const type = el.attrsMap.type
  const { lazy, number, trim } = modifiers || {}
  const needCompositionGuard = !lazy && type !== 'range'
  const event = lazy
    ? 'change'
    : type === 'range'
      ? RANGE_TOKEN
      : 'input'

  let valueExpression = '$event.target.value'
  if (trim) {
    valueExpression = `$event.target.value.trim()`
  }
  if (number) {
    valueExpression = `_n(${valueExpression})`
  }

  let code = genAssignmentCode(value, valueExpression)
  if (needCompositionGuard) {
    code = `if($event.target.composing)return;${code}`
  }

  addProp(el, 'value', `(${value})`)
  addHandler(el, event, code, null, true)
  if (trim || number || type === 'number') {
    addHandler(el, 'blur', '$forceUpdate()')
  }
}

 再看代码的其他部分,并没有发现什么还可能出现问题的地方,所以直接来测试一波是否当我们输入汉字后再输入数字时,composing的判断出现了问题,导致了v-model无法获取值。

limit(e) {
      e.target.value = e.target.value.replace(/[^\d]/g,'')
      console.log('是否由输入法触发-----'+e.target.composing);
      console.log('value值-----'+e.target.value)
      console.log('model值-----'+this.loginForm.phoneNumber)
    }

果然,composing再继续输入数字时,每次判断仍然为true,导致了v-model无法获取到值。

为什么v-model会加一层composing判断?经过一番查阅之后,终于发现了原因。看如下代码:

<input :value="name" @input="format">

format ($event) {
      this.name = $event.target.value.toUpperCase()
    }

当我们输入中文时会发现,拼音全部转化成了大写字母出现在了输入框中,很明显,这是有问题的。所以v-model在实现时,加上了composing这一判断。

因为这个原因,所以elementui的inputnumber输入框也是使用change事件判断输入的内容。

但是为什么composing判断会出现问题?

composing 属性并不是标准 dom 元素属性,那它是怎么来的呢?

这里可以看出,composing 属性是 vue.js 添加到 dom 节点上的。

那么,是什么地方调用了这2个函数呢?可以看到,在插入dom节点时,vue.js 监听了 compositionstart / compositionend 事件

compositionstart / compositionend 这2个 dom 事件,浏览器兼容性问题可以查阅 MDN 说明:

compositionstart 事件

compostionend 事件

v-model 实际上是监听了 <input> 控件的 input、compositionstart、compositionend 三个事件,在输入法组合过程中就直接返回不赋值v-model 指令设置了变量 composing,此标识还用于判断是否更新 dom元素的 value 属性

v-model 输入中文触发的事件

从上面源代码分析可知,v-model 绑定 <input> 输入中文时,实际触发的事件如下:

compositionstart => 3个 input => compositionend 事件,这些都是 <input>控件触发的。最后一个 input 事件,是源代码里面看到的 onCompositionEnd 回调里面 vue.js 触发的。

1、compositionstart事件

修改dom对象的composing属性为 true

2、3个input事件

由于dom对象的composing属性为true,不会赋值,直接返回。

3、compositioinend事件

修改dom对象的composing属性为 false

4、vue.js触发的 input事件

由于dom对象的composing属性为false,赋值,修改相应变量的值。

回到我们开头所说的问题,应该是当输入汉字时,因为是非数字,所以被正则匹配替换为空,汉字未显示,从而导致composing未正确的被更改为false。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值