Vue input 拦截控制输入内容

一直在用 react,对于控制 input 输入框内容处理起来非常方便。可是在 vue 项目中,这个处理就略显麻烦。查阅网上资源,大部分都是给出控制 keyup keydown 等,输入时候进行 replace 操作,我认为是有问题的。

比如有一个需求,要求支持输入 16 进制颜色值,并且为大写显示。

react 实现

用 react 来实现,不论是传统 class 类写法,还是新的 useHooks 写法都是很容易的。

主要是因为只需要监听输入内容,如果符合要求直接写入 state 就可以了,而 input 输入框的值绝对和 state 一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import './styles.css'

function App() {
const [color, setColor] = useState('')

const handleInput = e => {
const val = e.target.value
if (/^[\dA-Fa-f]{0,6}$/g.test(val)) {
setColor(val.toUpperCase())
}
}

return (
<div>
<div className="input">
<p>#</p>
<input onInput={handleInput} value={color} />
</div>
<p className="show">You input is: {color}</p>
</div>
)
}

const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.input {
display: flex;
align-items: center;
padding-bottom: 1px;
width: 150px;
height: 30px;
border-bottom: 1px solid #aaa;
}
.input p {
margin-right: 4px;
height: 20px;
}
.input input {
width: 100%;
height: 100%;
border: 0;
outline: none;
}
.show {
margin-top: 50px;
}

vue 实现

由于 v-model 会自动绑定数据,导致没办法拦截输入,所以不能使用。而 keyup keydown 经过测试,体验也非常不好,还不如 input 省心。

  • keydown 可以做到精准拦截,通过 return false 让输入内容不能输入到 input 上,但该方法也导致无法直接获取用户输入的内容(只能获取键盘按键 key 值)。
  • keyupinput 实现方式应该一致,但我一开始实验时候没想到复写 value,导致体验不太好,具体细节原因忘记了。

实现控制输入的核心在于,先获取用户输入内容,如果合格则设置新值,不合格还要还原成旧值!这个过程存在新值和旧值的比对,所以要记录好旧值。

同时,需要改写 state 值以及 input 的 value 值,因为手动修改 input 值需要自己改变 state 状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<div class="input">
<p>#</p>
<input @input="onInput" :value="color">
</div>
<p class="show">You input is: {{color}}</p>
</div>
</template>

<script>
export default {
name: "App",
data() {
return {
color: ""
}
},
methods: {
onInput(e) {
const lastVal = this.color
const newVal = e.target.value

const valid = /^[\dA-Fa-f]{0,6}$/g.test(newVal)
e.target.value = valid ? newVal.toUpperCase() : lastVal
this.color = valid ? newVal.toUpperCase() : lastVal
}
}
}
</script>

<style>
<!-- 样式参考 react 方案中 css 内容 -->
</style>

vue 组件封装方案

为了省事,可以把 input 进行封装成组件。

下面例子中,仅仅封装了 input value 功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<input @input="onInput" :value="value">
</template>

<script>
export default {
props: ['value'],
data() {
return {
val: this.value
}
},
watch: {
value(newVal, oldVal){
this.val = newVal
}
},
methods:{
onInput(e) {
const newVal = e.target.value
e.target.value = this.val
this.$emit('input', newVal)
}
}
}
</script>

调用上和 react 思路基本一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div>
<div class="input">
<p>#</p>
<Input @input="onInput" :value="color"/>
</div>
<p class="show">You input is: {{color}}</p>
</div>
</template>

<script>
import Input from './Input'

export default {
name: 'App',
components: { Input },
data() {
return {
color: ''
}
},
methods: {
onInput(val) {
const upper = val.toUpperCase()
if(/^[\dA-F]{0,6}$/g.test(upper)) {
this.color = upper
}
}
}
};
</script>

<style>
<!-- 样式参考 react 方案中 css 内容 -->
</style>

–END–