zoukankan      html  css  js  c++  java
  • 解决 React 中的 input 输入框在中文输入法下的 bug

    以下会涉及到的技术点:react mobx compositionstart compositionupdate compositionend

    问题描述

    在使用 input 时,通常会对输入的内容做校验,校验的方式无非两种:

    1. 允许用户输入,并且做错误提示;
    2. 不允许用户输入正则或者函数匹配到的字符。

    现有如下需求:“仅允许输入英文、数字和汉字,不允许输入其他特殊字符和符号”。显然这种场景需要使用第二种校验方式。

    然后我自以为很机智的写了下面的代码(引入了组件库 cloud-react),在输入值变化的时候(onChange 事件),处理绑定到 input 上的 value,将除了英文、数字、和汉字之外的字符都替换成空字符串。

    export default class CompositionDemo extends Component {
      constructor() {
         this.state = {
           value: ''
         };
      }
      
      onChange(evt) {
         this.setState({
           value: evt.target.value.replace(/[^a-zA-Z0-9u4E00-u9FA5]/g, '')
         });
      };
      
      render() {
        return <Input
            onChange={this.onChange.bind(this)}
              value={this.state.value}
           />
      }
    }

    平平常常,普普通通,一切看起来都是正常的操作,结果,当我输入拼音的时候,神奇的事情发生了:连拼的时候除了最后一个字,前面的都变成了字符。

    what??? 小问号,你是否有很多朋友?

    于是,我踏上了一条不归路,呸呸呸,是打开了新世界的大门,就是这个门对于我来说可能有点沉,推了两天才看到新世界。

    纠其原因:拼音输入是一个过程,确切的说,在这个过程中,你输入的每一个字母都触发了 onChange 事件,而你输入过程中的这个产物在校验中被吃掉了,留下了一坨空字符串,所以就发生了上面那个神奇的现象。

    vi设计http://www.maiqicn.com 办公资源网站大全https://www.wode007.com

    解决方案

    这里需要用到两个属性:compositionstart、compositionend

    简单点来说,就是当你开始使用输入法进行新的输入的时候,会触发 compositionstart ,中间过程其实也有一个函数 compositionupdate,顾名思义,输入更新时会触发它;当结束输入法输入的时候,会触发 compositionend。

    下面进入正题:

    首先,我们先看一下 Input 组件的一个很正常的实现:

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    
    export default class InputDemo1 extends Component {
    
        constructor(props) {
            super(props);
            this.state = {
                value: '',
            };
        }
        static getDerivedStateFromProps({ value }, { value: preValue }) {
            if (value !== preValue) {
                return { value };
            }
            return null;
        }
    
        onChange = evt => {
            this.props.onChange(evt);
        };
    
        render() {
            return <input
                value={this.state.value}
                type="text"
                onChange={this.onChange}
            />
        }
    }

    Input 组件有两种应用场景:

    1. 不受控的输入框:业务方不给组件传入 value,无法控制输入框的值;
    2. 受控的输入框:业务方可以通过给组件传入 value,从而可以在外部控制输入框的值。

    不受控的输入框在我使用过程中并没有什么 bug,此处不做赘述,此处只谈受控的输入框,也就是我们需求(仅允许输入英文、数字和汉字,不允许输入其他特殊字符和符号)中需要使用的场景。

    前面提到的 compositionstart 和 compositionend 该出场了:利用这两个属性的特点,在输入拼音的“过程中”不让 input 触发 onChange 事件,自然就不会触发校验,好了,既然有了思路,开始码代码。

    我们定义一个变量 isOnComposition 来判断是否在“过程中”

    isOnComposition = false;
    
    handleComposition = evt => {
      if (evt.type === 'compositionend') {
        this.isOnComposition = false;
        return;
      }
    
      this.isOnComposition = true;
    };
    
     onChange = evt => {
       if (!this.isOnComposition) {
         this.props.onChange(evt);
       }
     };
    
    render() {
      const commonProps = {
        onChange: this.onChange,
        onCompositionStart: this.handleComposition,
        onCompositionUpdate: this.handleComposition,
        onCompositionEnd: this.handleComposition,
      };
      return <input
        value={this.state.value}
        type="text"
          {...commonProps}
      />
    }

    你以为就这么轻松解决了么?

    呵,你想多了!

    我仍然使用开篇那个 demo 来测试这个代码,发现事情又神奇了一点呢,这次拼音压根就输不进去了哇~

    我查看了下在输入拼音时函数的调用:
    是的,宁没有看错,只触发了onCompositionstart 和 onCompositionupdate这两个函数,我起初以为是逻辑被我写扣圈了,想了想原因(其实我想了好久,人略笨,见笑):

    罪魁祸首就是绑定在 input 上的那个 value,输入拼音的过程中,state.value 一直没变,input 中自然不会有任何输入值,没有输入值,也就完成不了输入过程,触发不了 compositionend,一直处于“过程中”。

    所以这次不是程序逻辑扣圈,是中断了。

    于是我又想如何把中断的程序接起来(对的,垮掉了我们就捡起来,哈),完成这个链条。

    我想了好多办法,也在网上看了好多办法,可惜都解决不了我的困境。

    各种心酸不堪回首,幸好最后找到了一个办法:其实想想原来代码中用 state.value 去控制 input 值的变化,还是没有把 input 中何时输入值的控制权放在自己手里,“过程中”这个概念也就失去了意义。只要 state.value 还和 input 绑在一起,就是我自己玩我自己的,人家玩人家的。于是,就有了下面让控制权回到我手中的代码。

    import React, { Component, createRef } from 'react';
    import PropTypes from 'prop-types';
    
    export default class InputDemo extends Component {
    
        inputRef = createRef();
    
        isOnComposition = false;
    
        componentDidMount() {
            this.setInputValue();
        }
    
        componentDidUpdate() {
            this.setInputValue();
        }
    
        setInputValue = () => {
            this.inputRef.current.value = this.props.value || ''
        };
    
        handleComposition = evt => {
            if (evt.type === 'compositionend') {
                this.isOnComposition = false;
                return;
            }
    
            this.isOnComposition = true;
        };
    
        onChange = evt => {
            if (!this.isOnComposition) {
                this.props.onChange(evt);
            }
        };
    
        render() {
            const commonProps = {
                onChange: this.onChange,
                onCompositionStart: this.handleComposition,
                onCompositionUpdate: this.handleComposition,
                onCompositionEnd: this.handleComposition,
            };
            return <input
                ref={this.inputRef}
                type="text"
                {...commonProps}
            />
        }
    }

    测了一下,大致上是没问题了。

    还要看一下谷歌浏览器和火狐浏览器,果然还有坑:

    1. 火狐浏览器中的执行顺序:compositionstart compositionend onChange
    2. 谷歌浏览器中的执行顺序:compositionstart onChange compositionend

    最后再做一下兼容处理,修改一下 handleComposition 函数

    handleComposition = evt => {
       if (evt.type === 'compositionend') {
         this.isOnComposition = false;
    
         // 谷歌浏览器:compositionstart onChange compositionend
         // 火狐浏览器:compositionstart compositionend onChange
         if (navigator.userAgent.indexOf('Chrome') > -1) {
           this.onChange(evt);
         }
    
         return;
       }
    
       this.isOnComposition = true;
     };

    因为不管中间执行了那些函数,最后都是需要执行 onChange 事件的,因此加了判断,对谷歌浏览器做了特殊处理(其它浏览器暂时没做考虑和处理)。

    后记

    到此,正文结束了,我还要说两个需要注意的地方,其实也是踩了的坑:

    1. 如果 Input 组件的实现使用了 React.PureComponent ,在以上需求中会出现的问题:输入特殊字符时,外部通过正则将其 replace 掉了,传入 Input 组件内部的 value 实际上没有任何变化,也不会触发组件 render。这是因为 PureComponent 对 shouldComponentUpdate 函数做了优化,如果发现 props 和 state 上的属性都没有变化,不会重新渲染组件,因此我暂时的处理是:使用 React.Component ,组件实现中对 shouldComponentUpdate 封装。
    2. 在外部使用 mobx 的时候,如果使用 observable 监听 value,会出现和上面蕾丝的情况—输入特殊字符时,通过正则将其 replace 掉了,mobx 也发现 value 没有任何变化,就不会触发 render,我暂时的处理是使用 state,虽然我觉得这不是最好的办法,但我目前还想不到其它的处理方式。
  • 相关阅读:
    ASP.NET 2.0 用户注册控件的密码验证问题
    编程使用GridView,DataList的模版列
    在您的站点上添加 Windows Live Favourites 收藏入口
    推荐个很好玩的开源项目Ascii Generator dotNET
    Castle ActiveRecord 在Web项目和WinForm项目中
    HTML解析器项目进展和新的构思
    SilverLight 的跨域跨域访问
    SQL 语句之Join复习
    【笔记】提高中文分词准确性和效率的方法
    ASP.NET 动态加载控件激发事件的问题
  • 原文地址:https://www.cnblogs.com/xiaonian8/p/13705311.html
Copyright © 2011-2022 走看看