前言

最近做过不少的移动端表单提交的项目,跳了一些坑,也学习了一些技巧,总结了一下,等以后再做类似项目的时候拿来借鉴。同时也与大家分享。

常见的移动端表单校验问题

    1. 电话格式校验
    1. 邮箱格式校验
    1. 汉字VS字母输入
    1. value绑定与防抖
    1. input框折行展示
    1. 数字键盘的唤起
    1. 提交按钮联动校验
    1. 错误提示批量处理

电话格式校验

  1. 手机号码:网上有很多校验手机的正则,但是随着手机号码格式的增多,严格的校验可能导致在不久的将来,你的代码就需要重新上线,以适应更多的号码区段。如果再考虑到国际长途,可能不仅仅是11位。所以建议只使用纯数字校验,关于位数因需求而异。

    1
    let reg = /^\d{0,11}$/;
  2. 400电话:只做数字和位数的校验

    1
    let reg = /^[400]\d{0,n}$/;
  3. 座机号码:做数字和位数的校验

    1
    let reg = /^\d{4,n}$/;

邮箱格式校验

  1. 首来看几个合法邮箱的例子:

    1234@qq.com(纯数字)
    wang@126.com(纯字母)
    wang123@126.com(数字、字母混合)
    wang123@vip.163.com(多级域名)
    wang_email@outlook.com(含下划线 _)
    wang.email@jd.com(含英语句号 .)

  2. 根据对以上邮箱的观察,可将邮箱分为两部分(“@”左边和右边部分)来进行分析:

    左边部分可以有数字、字母、下划线(_)和英语句号(.),因此可以表示成:[A-Za-z0-9]+([_.][A-Za-z0-9]+)*
    右边部分是域名,按照域名的规则,可以有数字、字母、短横线(-)和英语句号(.),另外顶级域名一般为 2 ~ 6 个英文字母(比如“cn”、“com”、“site”、“group”、“online”),故可表示为:([A-Za-z0-9-]+.)+[A-Za-z]{2,6}

    要注意两点:

    考虑到匹配邮箱时字符串的一头一尾不能有其它字符,故要加上开始标志元字符 ^ 和结束标志元字符 $。
    英语句号(.)是正则表达式的元字符,因此要进行转义(.)。

  3. 用于邮箱验证的函数及测试用例如下:

    考虑可能扩展的邮箱后缀,关于位数可以放开

    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
    35
    let 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绑定与防抖

考虑移动端的校验经常是边输入边校验,我们就有必要做一些性能优化,比如常见的防抖

  1. 减少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>
    )
    }
  2. 在一些输入框中做校验,为了节约性能,不做不必要的频繁校验,需要使用防抖。

    防抖

    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种:

    1. 放一个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垂直居中。然后动态判断输入文字高度,动态设置文本框的高度。

    1. 放一个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框并且使其聚焦,聚焦这个动作有的不能实现。

    解决
    加上延时后,可能会有改善的情况,需测试

    1. 放一个div给它一个contentEditable属性;

    优点
    没有动态设置的属性和操作

    缺点
    安全性不好;
    不能设置placeholder;

    解决
    可以手动放placeholder;毕竟前端黑科技很多。目前我没有使用,因为查找有很多坑,就没敢用,感兴趣的小伙伴可以试试。

数字键盘的唤起

在部分要求只输入数字的input框中,要求唤起数字键盘;

示例
示例

    1. 使用 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”**并不友好

    1. 使用 type=”tel”
      1
      2
      <input className="detail__input" type="tel" value={phone} maxLength={11} onChange={this.setPhone} placeholder="请输入收票人手机"/>

      可调用(有英文字母的大概)键盘,能获取到输入的数字以及各种符号 ,能用正则表达式限制输入内容!!!一般情况下可用这个

提交按钮联动校验

随着移动端用户体验不断优化,有些场景要求表单按钮的提交状态呈现动态提示。即要求用户每一次动作都和按钮的高亮和置灰联动。
这要求每一步操作都有全局的校验。如果处理不好,不仅代码看起来重复冗长,对性能也有较大影响。所以需要注意以下几点:

  1. 全局的优先级较高的校验,放在前面,如果信息未填写完整的情况下,在输入某个文本框的自身校验时不再进行全局校验。
  2. 封装方法,校验统一的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
    }
    }

错误提示批量处理

  1. 如果表单提交项交多,有相似的输入结构放在同一个方法处理。
  2. 封装一个全局统一的提示方法,通过传入不同的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
    54
    checkoutAllItem () {
    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
    },