该指令主要解决了以下问题:
1,v-model 同步问题
2, 中文输入法导致 input 触发了但 v-model 同步失效
3, 可扩展到其他 UI 框架下, 如 element
通常限制一个 input 仅能输入指定字符我们会去监听其 input 事件, 然后通过正则过滤掉非法字符. 然而在 vue 中, 仅仅修改 input 的 value 并不会同步到 v-model 上. 在 vue 官方文档中有提到 v-model 其实是 v-bind 和 v-on 合起来的语法糖, 如下:
<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)">
由此可以看出 v-model 的更新其实也是基于监听 input 事件, 但由于我们直接修啊修改 input 的 value 并不会触发 input 事件, 所以我们只须手工去触发一下这个事件即可. 按此逻辑可以先写出一个基础版的指令, 如下:
- Vue.directive('inputInt', {
- bind(el, binding, vnode) {
- let input = vnode.elm;
- input.addEventListener('input', () => {
- let oldValue = input.value;
- let newValue = input.value.replace(/[^\d]/g, '');
- // 判断是否需要更新, 避免进入死循环
- if(newValue !== oldValue) {
- input.value = newValue
- input.dispatchEvent(new Event('input')) // 通知 v-model 更新
- }
- })
- }
- })
注意, 我们通过手工触发 input 事件会再次进入指令, 如此就成了死循环, 所以此处需要判断是否需要去更新 v-model, 进而确定是不是需要手工去触发事件.
以上代码看上去是 ok 的, 但实际使用时会遇到一个很奇怪的现象: 当用中文输入法时, 尝试输入中文的字符确实会被过滤掉, 但 v-model 并没有同步, 再输入数字时又正常了 (在 element 下若输入中文, v-model 将永久不会再同步更新). 这个现象暴露出一个很明显的问题, 当我们尝试输入中文是, 每敲一个字母就会触发一次 input 事件, 而我们期望的是在确认输入的时候才去校验. 幸运的是浏览器提供了一组事件去处理这样的情况 compositionstart,compositionend.
MDN 释义如下:
compositionstart 事件触发于一段文字的输入之前 (类似于 keydown 事件, 但是该事件仅在若干可见字符的输入之前, 而这些可见字符的输入可能需要一连串的键盘操作, 语音识别或者点击输入法的备选词).
当文本段落的组成完成或取消时, compositionend 事件将被触发 (具有特殊字符的触发, 需要一系列键和其他输入, 如语音识别或移动中的字词建议).
通过测试发现, 我们可以通过这组事件去控制是否触发 input 事件, 同时也避免了中文输入法导致 v-model 无法同步的情况发生. 一个完整的输入整数指令如下:
- Vue.directive('inputInt', {
- bind(el, binding, vnode) {
- let input = vnode.elm;
- input.addEventListener('compositionstart', () => {
- vnode.inputLocking = true
- })
- input.addEventListener('compositionend', () => {
- vnode.inputLocking = false
- input.dispatchEvent(new Event('input'))
- })
- input.addEventListener('input', () => {
- if(vnode.inputLocking) {
- return;
- }
- let oldValue = input.value;
- let newValue = input.value.replace(/[^\d]/g, '');
- // 判断是否需要更新, 避免进入死循环
- if(newValue !== oldValue) {
- input.value = newValue
- input.dispatchEvent(new Event('input')) // 通知 v-model 更新
- }
- })
- }
- })
至此, 以上问题都以解决. 当我们想要对更多情况做控制时, 只需更改 value 的值即可.
扩展一个两位浮点数指令
- Vue.directive('inputFloat', {
- bind(el, binding, vnode) {
- let input = vnode.elm;
- input.addEventListener('compositionstart', () => {
- vnode.inputLocking = true
- })
- input.addEventListener('compositionend', () => {
- vnode.inputLocking = false
- input.dispatchEvent(new Event('input'))
- })
- input.addEventListener('input', () => {
- if(vnode.inputLocking) {
- return;
- }
- let oldValue = input.value;
- let newValue = input.value;
- newValue = newValue.replace(/[^\d.]/g, '');
- newValue = newValue.replace(/^\./g, '');
- newValue = newValue.replace('.', '$#$').replace(/\./g, '').replace('$#$','.');
- newValue = newValue.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3')
- // 判断是否需要更新, 避免进入死循环
- if(newValue !== oldValue) {
- input.value = newValue
- input.dispatchEvent(new Event('input')) // 通知 v-model 更新
- }
- })
- }
- })
扩展到其他 UI 框架
其实扩展到其他框架很简单, 整个指令对元素的依赖仅仅表现在节点的获取上, 在不同的框架下更改获取 input 节点相应的代码即可. 如在 element 下获取节点的代码为:
let input = vnode.elm.children[0];
在线预览
以上, tks~
来源: http://www.jianshu.com/p/55b99757955b