IOS10.3发布后对CompositionEvent 状态的更改

当我要打一个中文字,比如 ,那么我就要输入 ri 两个字母,而在某些时候我输入的字是要跟后端有数据交互的,那么在一些情况下我原本只想跟后端 交互 这一个字,然而因为 的拼音是ri 所以可能在我键入==的时候 已经分别与后端进行了两次2交互了,一次是r,一次是ri==,这明显不符合我们需要的运作逻辑;

首先我们先来看一篇文章:

摘选自 segmentfault

中文输入法与React文本输入框的问题与解决方案

问题来源是来自这个React官方存储库的issue #3926,与这个议题关联的有很多其他的issue,来自许多项目,有些是与React相关,有些则是vue或其它JS套件。也已经有其他的项目是专注于解决这个问题,例如react-composition,不过它是一个使用ES5语法的React组件。在其他的讨论区上也有类似的问题与解答。本文的目的是希望能针对这个问题提供一些说明、现在暂时性的解决方案。
onChange在浏览器上,只要在这个文本输入框上,有任何的键盘动作它都会触发,也就是如果你是使用了中文、日文、韩文输入法(IME),不论是哪一种,拼音的、笔划的还是其他的,只要有按下一个键盘的动作,就会触发一次浏览器上这个元素的change事件,对于原本就使用键盘上的英文字符作为输入的语言来说,这没什么太大的问题,但对于要使用输入法的语言用户来说,不停的触发change事件,可能会造成程序功能上的运行逻辑问题。
举出一个实际的应用情况,一个使用React撰写的搜索计算机书籍的功能,用户可以在文本输入框里输入要搜索的书名,程序中是利用onChange事件触发,进行比对数据库中的书籍标题,当你想搜索一本名为"林哥的Java教程",第一个字为"林",拼音输入法需要输入"lin"三个键盘上的字符,在"林"这个字从输入法编辑器中加到真正的input元素前,onChange已经捕捉到"lin"三个字符,在列表中已搜索出一大堆有关"linux"的书籍。细节就不说了,还有可能对字符数量的的检查之类的问题。不过,这是正确的程序运作逻辑吗?很明显的这是一个大问题。
当然,你也可以用对中文字词检查的修正方式,或是干脆不要用change事件,改用其他按钮触发之类的事件来作这事情
这个问题在浏览器中,早就已经有了可应对的解决方法,DOM事件中有一组额外的CompositionEvent(组成事件)可以辅助开发者,它可以在可编辑的DOM元素上触发,主要是input与textarea上,所以可以用来辅助解决change事件的输入法问题。CompositionEvent(组成事件)共有三个事件,分别为compositionstart、compositionupdate与compositionend,它们代表的是开始进行字的组成、刷新与结束,也就是代表开始以输入法编辑器来组合键盘上的英文字符,选字或刷新字的组合,到最后输出字到真实DOM中的文本输入框中,实务上每个中文字在输入时,compositionstart与compositionend都只会会被触发一次,而compositionupdate则是有可能多次触发。
藉由CompositionEvent的辅助来解决的方式,也就是说在网页上的input元素,可以利用CompositionEvent作为一个信号,如果正在使用IME输入中文时,change事件中的代码就先不要运行,等compositionend触发时,接着的change事件才可以运行其中的代码,运作的原理就是这样简单而已。
这个解决方案在几乎所有能支持CompositionEvent的浏览器(IE9以上)都可以运行得很好,不过在Google Chrome浏览器在2016年的版本53之后,更动了change与compositionend的触发顺序,所以需要针对Chrome浏览器调整一下,如果是在Chrome浏览器中触发compositionend时,也要运行一次在原本在change要运行的代码。

接下来我们来回到正题上

上文中所说的方法可以解决90%的 拼音输入问题,但是最近IOS10.3发布了,于是乎新的问题出现了!ios10.3对 compositionEvent状态的触发顺序做了调整,变得跟chrome一样了,也就是说 如果对方使用的 是 ios10.3版本的系统,那么就要对他进行额外的处理,在compositionEnd之后手动触发一次change事件。。。

接下来我们来整理一下代码:(以下我们以React ES6的方式来写代码,其他写法类同,请自行调整)

首先browserDetection.js

/**
* Created by Luna_Shu on 2017/3/30.
* 
* @author Luna_Shu
* @description 判断浏览器
* 
*/
// Opera 8.0+
export const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
// Firefox 1.0+
export const isFirefox = typeof InstallTrigger !== 'undefined';
// Safari 3.0+ "[object HTMLElementConstructor]"
export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] ||     safari.pushNotification);
// 是否是IOS系统
export const isIOS = navigator.userAgent.indexOf('iPhone') >= 0 || navigator.userAgent.indexOf('iPad');
// 获取IOS系统版本
export const IOSVer = navigator.userAgent.match(/\\d[\\d]*_\\d[_\\d]*/i) && parseFloat(navigator.userAgent.match(/\\d[\\d]*_\\d[_\\d]*/i)[0].split('_').join('.'));
// Internet Explorer 6-11
export const isIE = /*@cc_on!@*/false || !!document.documentMode;
// Edge 20+
export const isEdge = !isIE && !!window.StyleMedia;
// Chrome 1+
export const isChrome = !!window.chrome;
// Blink engine detection
export const isBlink = (isChrome || isOpera) && !!window.CSS;

然后 input.js

import * as browserTools from './browserDetection'; // 导入浏览器识别
let isComposition = false; // 创建 状态变量 是否是拼音输入状态(当然也可以在 构造方法中创建 this.state={isComposition: false} )
class RightBarPopSelect extends React.Component{
    constructor(){
        super();
    }
    render(){
        return (
            <div>
                <input 
                    type="text"
                    className="u-form-control f-ta-c f-mt-20"
                    value={this.state.filterValue}
                    placeholder="筛选"
                    onChange={this.handleChange.bind(this)}
                    onCompositionStart={this.handleCompsition.bind(this)}
                    onCompositionUpdate={this.handleCompsition.bind(this)}
                    onCompositionEnd={this.handleCompsition.bind(this)}
                />
            </div>
        )
    }
    handleCompsition(e){ // 拼音输入事件
        if(e.type === 'compositionend'){ // 判断输入状态是否 是 拼音输入结束 状态 
            isComposition = false;
            ...       
            if(browserTools.isChrome || (browserTools.isIOS && browserTools.IOSVer >= 10.3) ){ // 判断浏览器是否是Chrome 或者 是否是IOS系统以及 IOS版本是否 大于等于10.3 
            // 如果是 就执行 change方法                   
                this.handleChange(e);
            }
        }else{
            isComposition = true;
        }
    }
    
    handleChange(e){ // change事件
        if (!isComposition) {
            ...
        }
    }