前言
最近做过不少的移动端表单提交的项目,跳了一些坑,也学习了一些技巧,总结了一下,等以后再做类似项目的时候拿来借鉴。同时也与大家分享。
常见的移动端表单校验问题
- 电话格式校验
- 邮箱格式校验
- 汉字VS字母输入
- value绑定与防抖
- input框折行展示
- 数字键盘的唤起
- 提交按钮联动校验
- 错误提示批量处理
电话格式校验
手机号码:网上有很多校验手机的正则,但是随着手机号码格式的增多,严格的校验可能导致在不久的将来,你的代码就需要重新上线,以适应更多的号码区段。如果再考虑到国际长途,可能不仅仅是11位。所以建议只使用纯数字校验,关于位数因需求而异。
1
let reg = /^\d{0,11}$/;
400电话:只做数字和位数的校验
1
let reg = /^[400]\d{0,n}$/;
座机号码:做数字和位数的校验
1
let reg = /^\d{4,n}$/;
邮箱格式校验
首来看几个合法邮箱的例子:
1234@qq.com(纯数字)
wang@126.com(纯字母)
wang123@126.com(数字、字母混合)
wang123@vip.163.com(多级域名)
wang_email@outlook.com(含下划线 _)
wang.email@jd.com(含英语句号 .)根据对以上邮箱的观察,可将邮箱分为两部分(“@”左边和右边部分)来进行分析:
左边部分可以有数字、字母、下划线(_)和英语句号(.),因此可以表示成:[A-Za-z0-9]+([_.][A-Za-z0-9]+)*
右边部分是域名,按照域名的规则,可以有数字、字母、短横线(-)和英语句号(.),另外顶级域名一般为 2 ~ 6 个英文字母(比如“cn”、“com”、“site”、“group”、“online”),故可表示为:([A-Za-z0-9-]+.)+[A-Za-z]{2,6}要注意两点:
考虑到匹配邮箱时字符串的一头一尾不能有其它字符,故要加上开始标志元字符 ^ 和结束标志元字符 $。
英语句号(.)是正则表达式的元字符,因此要进行转义(.)。用于邮箱验证的函数及测试用例如下:
考虑可能扩展的邮箱后缀,关于位数可以放开
1
let reg = /^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,n}$/;
React中-汉字VS字母输入
Vue中的v-model已经做了对于输入中文的处理,这里我们不多说,感兴趣的同学,可以查看相关文档中。
在React输入汉字的文本框中,有时候我们会遇到书写‘我们’,直接输入了‘women’;这样的情况,可以考虑使用‘compositionend’
首先了解三个事件
要开始输入中文
compositionstart插入新字符
compositionupdate输入完成
compositionend这样就可以做到在普通情况下监听 input 事件,而当监听到 compositionstart 事件时,移除对 input 事件的监听或不处理监听到的 input 事件,而监听 compositionend 事件并处理。
此处注意的是compositionend是在onChange方法之后触发的,所以需要如下: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
35let isOnComposition = true
setInvoiceTitle = ({target}) => {
const value = target.value
/*
if (!isOnComposition) {//解决compositionend不触发问题
this.setState({
invoiceTitle: value
});
}
*/
this.setState({
invoiceTitle: value
})
}
handleComposition = (e) => {
if (e.type === 'compositionend') {
isOnComposition = false
} else {
isOnComposition = true
}
}
render(){
return (
<div className="detail__item" >
<div className="detail__lable" >抬头名称</div>
<div className="detail__value_box" >
<input className="detail__input" onChange={this.setInvoiceTitle} onCompositionStart={this.handleComposition}
onCompositionEnd={this.handleComposition} type="text" placeholder={"请填写单位名称"}/>
</div>
</div>
)
}
value绑定与防抖
考虑移动端的校验经常是边输入边校验,我们就有必要做一些性能优化,比如常见的防抖
减少value绑定;一些情况下,我们是没有必要将input框的value值与我们所需要的值进行绑定的。这样的情况下我们就不要做绑定,可以减少性能消耗。
减少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
27
28//减少value绑定
setInvoiceTitle = (e) => {
const value = e.target.value
this.setState({
invoiceTitle: value
})
}
handleComposition = (e) => {
if (e.type === 'compositionend') {
isOnComposition = false
} else {
isOnComposition = true
}
}
render(){
const { invoiceTitle } = this.state
return (
<div className="detail__item" >
<div className="detail__lable" >抬头名称</div>
<div className="detail__value_box" >
<input className="detail__input" value={invoiceTitle} onChange={this.setInvoiceTitle} onCompositionStart={this.handleComposition}
onCompositionEnd={this.handleComposition} type="text" placeholder={"请填写单位名称"}/>
</div>
</div>
)
}在一些输入框中做校验,为了节约性能,不做不必要的频繁校验,需要使用防抖。
防抖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//防抖
let timer = null
debounce = (callback) => {
clearTimeout(timer);
timer = setTimeout(() => {
callback && callback()
}, 600);
}
setInvoiceTitle = (e) => {
const value = e.target.value
let setTitle = () => {
this.setState({
invoiceTitle: value
})
}
this.debounce(setTitle)
}
input框折行展示
表单常见的输入还有一种情况是折行,如图:
这样的效果,我考虑到的方法有3种:
- 放一个textarea框,可以支持多行输入和展示;
1
2
3
4
//HTML
<textarea className={TextareaMore?'detail__textarea_more':"detail__textarea"} onCompositionStart={this.handleComposition} onCompositionEnd={this.handleComposition} onChange={this.setAddressText} placeholder="请输入详细地址"></textarea>优点
可以设置placeholder属性值;多行展示;多行输入;缺点
高度不能自适应,如果高度写死,输入一行和两行不同情况的时候,文字上下会不居中解决
默认一行的高度,这样可以保证placeholder垂直居中。然后动态判断输入文字高度,动态设置文本框的高度。- 放一个textarea框,可以支持多行输入和展示;
- 放一个input框和一个div,等到失焦的时候显示div,聚焦的时候隐藏div;
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
//JS
showInputClick = () => {
this.setState({
showInput: true
})
setTimeout(() => {
this.refs.input.focus()
}, 60)
}
onInput = (e) => {
const value = e.target.value
this.setState({
addressText: value
})
}
onBlur = () => {
this.setState({
showInput: false
})
}
//HTML
<input className={showInput?'showDom':"hideDom"} ref='input' onInput={this.onInput} onBlur={this.onBlur} type="text" placeholder="请输入详细地址" value={addressText} />
<div className={showInput?'hideDom':"showDom"} onClick={this.showInputClick}>{addressText}</div>
优点
相对于textarea不需要动态设置高度。缺点
在失焦和聚焦的时候。动态隐藏和显示input框。安卓和ios有兼容问题。在点击div的时候显示input框并且使其聚焦,聚焦这个动作有的不能实现。解决
加上延时后,可能会有改善的情况,需测试- 放一个input框和一个div,等到失焦的时候显示div,聚焦的时候隐藏div;
- 放一个div给它一个contentEditable属性;
优点
没有动态设置的属性和操作缺点
安全性不好;
不能设置placeholder;解决
可以手动放placeholder;毕竟前端黑科技很多。目前我没有使用,因为查找有很多坑,就没敢用,感兴趣的小伙伴可以试试。
数字键盘的唤起
在部分要求只输入数字的input框中,要求唤起数字键盘;
- 使用 type=”number”
1
2
3<input className="detail__input" type="number" pattern="\d*" value={zip} onChange={this.setZip} placeholder="请输入邮政编码"/>
<input className="detail__input" type="number" pattern="[0-9]*" value={zip} onChange={this.setZip} placeholder="请输入邮政编码"/>
iOS中,只有[0-9]* 才可以调起九宫格数字键盘,\d 无效
Android 4.4以下(包括X5内核),两者都调起数字键盘;
Android 4.4.4以上,只认 type 属性,也就是说,如果上面的代码将 type=“number” 改为 type=“text” ,将调起全键盘而不会是九宫格数字键盘。
缺点
键盘调用显示有兼容性,即有的有小数点和加减号,有的没有。对于只要求数字的需求,使用**type=”number”**并不友好- 使用 type=”number”
- 使用 type=”tel”可调用(有英文字母的大概)键盘,能获取到输入的数字以及各种符号 ,能用正则表达式限制输入内容!!!一般情况下可用这个
1
2<input className="detail__input" type="tel" value={phone} maxLength={11} onChange={this.setPhone} placeholder="请输入收票人手机"/>
- 使用 type=”tel”
提交按钮联动校验
随着移动端用户体验不断优化,有些场景要求表单按钮的提交状态呈现动态提示。即要求用户每一次动作都和按钮的高亮和置灰联动。
这要求每一步操作都有全局的校验。如果处理不好,不仅代码看起来重复冗长,对性能也有较大影响。所以需要注意以下几点:
- 全局的优先级较高的校验,放在前面,如果信息未填写完整的情况下,在输入某个文本框的自身校验时不再进行全局校验。
- 封装方法,校验统一的check方法,不重复书写校验代码。
1
2
3
4
5
6
7
8
9
checkoutAllItemTrue () {
if( this.valueComp && this.addrFId && this.addrSId && this.addrTId && this.valueAddrD){
this.canotSubmit = false
}else{
this.canotSubmit = true
}
}
错误提示批量处理
- 如果表单提交项交多,有相似的输入结构放在同一个方法处理。
- 封装一个全局统一的提示方法,通过传入不同的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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54checkoutAllItem () {
if (!this.valueComp) {
this.$toast('请填写机构名称', {
type: 'error'
})
return false
}
if (this.addrFId != '' && this.addrSId != '' && this.addrTId != '') {
} else {
this.$toast('请选择机构所在地', {
type: 'error'
})
return false
}
if (!this.valueAddrD) {
this.$toast('请填写详细地址', {
type: 'error'
})
return false
}
if (!this.valueName) {
this.$toast('请填写姓名', {
type: 'error'
})
return false
}
if (!this.valueTel) {
this.$toast('请填写电话', {
type: 'error'
})
return false
}
let codeId = this.$refs.codeId.value
if (!codeId) {
this.$toast('请填写验证码', {
type: 'error'
})
return false
}
if (!this.valueMail) {
this.$toast('请填写邮箱', {
type: 'error'
})
return false
}
if (!this.fileList.length) {
this.$toast('请选择文件', {
type: 'error'
})
return false
}
return true
},