前言

无论是pc端还是移动端,在业务中,表单值的校验和搜索功能都是非常常见的需求。下面总结自己在使用vue、react实现这些功能时踩过的坑。

表单校验、搜索功能的实现

表单校验

表单校验需求中常见的例如:匹配某种规则(企业税号格式的校验、电话号码(手机号)的校验、身份证的校验...)、对内容长度限制、对输入字符类型的限制等等。

Vue中的实现:用input事件来对输入框的值进行处理

Vue中需要特别注意的是在处理中文时对事件input和keyup之间的选择,选择input事件。

坑点:
input事件和keyup事件在输入时触发事件的表现上没有区别,input是值改变就触发,keyup是键盘释放时就触发。当在输入中文未选内容鼠标离开时,不会触发keyup事件,但是会触发input事件。如下例子,限制输入框字符数不超过32个:

限制字符数代码(中文算2个,英文算1个):

 limitLength(e) {
            const input = e.target
            const value = input.value
            const split = value.split('')
            const map = split.map((s, i) => {
                return value.charCodeAt(i) >= 0 && value.charCodeAt(i) <= 128
                    ? 1
                    : 2
            })
            let n = 0
            const charLength =
                map.length > 0 &&
                map.reduce((accumulator, currentValue, index) => {
                    const count = accumulator + currentValue
                    if (count === 31 || count === 32) {
                        n = index
                    }
                    if (count > 32) {
                        this.name = split.slice(0, n + 1).join('')
                        // this.$emit("setUserName",split.slice(0, n+1).join(''));
                    }
                    return count
                })
        },

keyup事件效果

鼠标离开时没有触发回调函数(输入中文拼音时字符数超过了32个还能显示)。

input事件效果

鼠标离开,触发回调函数。

Vue中实现带校验功能的FormInput输入框组件:

<template>
 <input
   @input="methodKeyUp"
   v-model="currentValue"
 >
</template>

<script>
export default {
 name: 'HelloWorld',
 props: {
   msg: String,
 },
 data() {
   return {
     currentValue: '2222',
   };
 },
 methods: {
   limitLength(value, maxWordsCount) {
     return value && value.length > maxWordsCount
       ? value.slice(0, +maxWordsCount)
       : value;
   },

   async methodKeyUp(v) {
     const target = v.currentTarget || v.target;
     let arr = [];
     let val = target.value;
     if (val) {
       val = val.toString().trim();
       val = this.limitLength(val, 6);
       arr = val.match(/^[\u4e00-\u9fa5]+(\d+)?$/);
       if (
         arr &&
         Object.prototype.toString.call(arr) == '[object Array]' &&
         arr.length
       ) {
         val = arr[0];
       } else {
         val = '';
       }
       this.currentValue = val;
       
     }
   },
 },
};
</script>
额外说明: 在使用vue实现回车换行这样的功能时使用的keyup事件有更好的体验
keyup: function(e) {
            //Ctrl + Enter发送消息
            if (e.keyCode == 13 && e.ctrlKey) {
                this.content += `\n`
                let el = this.$el.querySelector('.input-textarea')
                el.scrollTop = el.scrollHeight - el.clientHeight
            } 
        },

校验与搜索并存

React中的实现:需封装原生的compositionstart/compositionend事件

React中input表单没有input事件,那么如何来实现像vue中input事件那样处理中文输入的效果呢?

FormInput业务组件:


 limitLength(value, maxWordsCount) {
      return value && value.length > maxWordsCount
        ? value.slice(0, +maxWordsCount)
        : value;
 },

  
//值改变先校验值再去查询
  filterInputValue = async (v: any) => {
    const target = v.currentTarget || v.target
    let arr = []
    let val = target.value
    //this.setState({ isShowOptions: true })
    await this.props.onHandleChange(val) //更新父组件value值
    if (val) {
      val = val.toString().trim()
      val = this.limitLength(val, 6)
      arr = val.match(/^[\u4e00-\u9fa5]+(\d+)?$/)
    }

    if (arr && Object.prototype.toString.call(arr) == '[object Array]' && arr.length) {
      val = arr[0]
    } else {
      val = ''
    }
    await this.props.onHandleChange(val) //更新父组件value值
    this.searchValue(val) //再查询
  }

React版自定义输入表单inputField组件,封装compositionstart/compositionend:

import * as React from 'react'
import PropTypes from 'prop-types'
import './input-field.less'
// detect whether it is chrome
// const isChrome = !!window.chrome && !!window.chrome.webstore

const noop = () => {}

interface  InputField {
   isOnComposition: Boolean,
   emittedInput: Boolean,
}

class InputField extends React.PureComponent<any, any> {

   static propTypes = {
       value: PropTypes.any,
       defaultValue: PropTypes.any,
       onChange: PropTypes.func,
       onInputChange: PropTypes.func
   }

   static defaultProps = {
       onChange: noop,
       onInputChange: noop
   }

   constructor(props:any, context:any) {
       super(props, context)
       this.state = {
           value: this.props.value || this.props.defaultValue || ''
       }
       this.isOnComposition = false
       this.emittedInput = true
   }

   componentWillReceiveProps(nextProps:any) {
       const { value } = nextProps
       if (value !== this.state.value) {
           this.setState({
               value
           })
       }
   }

   handleInputChange = (event:any) => {
       let userInputValue = event.target.value
       this.setState({
           value: userInputValue
       })
       if (!this.isOnComposition) {
           event.target.value = userInputValue
           this.props.onInputChange(event)
           this.emittedInput = true
       } else {
           this.emittedInput = false
       }
       this.props.onChange(userInputValue)
   }

   handleComposition = (event:any) => {
       if (event.type === 'compositionstart') {
           this.isOnComposition = true
           this.emittedInput = false
       } else if (event.type === 'compositionend') {
           this.isOnComposition = false
           // fixed for Chrome v53+ and detect all Chrome
           // https://chromium.googlesource.com/chromium/src/+/afce9d93e76f2ff81baaa088a4ea25f67d1a76b3%5E%21/
           // also fixed for the native Apple keyboard which emit input event before composition event
           // subscribe this issue: https://github.com/facebook/react/issues/8683
           if (!this.emittedInput) {
               this.handleInputChange(event)
           }
       }
   }

   handleKeyUp=(e:any)=> {
       const { onPressEnter, ...restProps} = this.props
       var event = e || window.event;
       var key = event.which || event.keyCode || event.charCode;
       if (key == 13) {
           onPressEnter(event)
           /*Do something. 调用一些方法*/ 
       }
   }

   render() {
       const { onInputChange, onBlur, onPressEnter, ...restProps} = this.props
       return (
           <div>
                   <input
                       type='text'
                       {...restProps}
                       value={this.state.value}
                       onChange={this.handleInputChange}
                       onCompositionStart={this.handleComposition}
                       onCompositionEnd={this.handleComposition}
                       onBlur={onBlur}
                       onKeyUp={this.handleKeyUp}
                   />
           </div>
       )
   }
}

export default InputField

总结

React和Vue中的input控件在处理中文时有些不同,Vue中通过@input事件可以直接在输入框输入中文并选中中文时再触发事件,但是React没有@input事件,需要对原生input表单的compositionStart和compositionEnd两个事件额外进行控制达到类似效果。


贝er
58 声望6 粉丝

不仅仅是程序员