问题:在React项目研发的过程中遇到一个输入框问题,在ios手机使用原生输入法输入中文时,待选拼音的变化也会触发change事件,导致input框内会把你敲的拼音和你确定选择的汉字一起显示在输入框中。

环境:react 17.x ; 组件库Ant Design Mobile  v5.17.1(对于TextArea,SearchBar,Input等组件都会存在该问题);
具体原因如下:引用了原作者的文章:

原因:在input的onChange中监听到input中输入的值的时候进行了正则的判断,而输入中文的拼音时候也会走onChange这个方法,到这个方法做正则的替换时候不知道现在输入的是拼音,直接当成字母来处理了,就出现了ios全键盘输入拼音时候还没有选择汉字拼音就直接转化成了字母。

效果:用ios原生输入 “吗”字时候:log日志展示,valuechange监听到了拼音以及两遍“吗”字,导致输入框内容展示出问题。(注:ZGDAa为初始值,可忽略不看)

ios 汉字根据拼音分组和排序 iphone汉字和拼音一起显示_ios

 

 

解决方法:

  使用组件原生属性的方法compositionstart+compositionend+input来判断直接输入的还是非直接直接输入

  • onCompositionStart:非直接的文字输入时(键盘输入中文的拼音)
  • onCompositionend:直接输入文字后(键盘选择真实的汉字)
  • onChange:输入框内容变化时触发

触发时机:

  onCompositionStart和onCompositionend只会在ios手机的原生输入法 输入中文时调用。如果输入英文时候,是不会触发onCompositionStartEnd和onCompositionStartStart事件的。如果input的value没有变化也不会触发onCompositionStartEnd事件。

onCompositionStart事件是在onChange之前触发,onCompositionEnd是在onChange之后触发。
注:在赋值时候 不要使用value,而需要使用defaultValue。因为当使用value时候,即作为受控组件,随着 输入数据的变化,input组件重新渲染,不再是之前的input,这时页面上每次输入内容的更新都带来整个组件的重新渲染,导致

onCompositionEnd事件没有被调用。(文字最后面附有 value赋值的错误案例 )

 

代码原理: 将Textarea封装为一个子组件,如果输入中文拼音时候,只有在选择完汉字时才调用父组件的valueChange方法将最新值传给父组件,父类进行相应数据的更新后 再把新值传递给子组件,子组件这时候更新Textarea标签所绑定的value值。即: 定义一个inputFlag,在用户开始中文拼音输入时,调用onCompositionStart方法 更新inputFlag为true,此时用户输入内容 不进行dataChange的更新,直到用户停止拼音输入选择了某个汉字后,触发onCompositionEnd方法,更新inputFlag为false,并将此时输入框的值传给父组件,调用onDataChange方法更新。

具体代码:

//子组件Children代码
import React, { FC, useContext, useEffect, useState } from 'react';
import { TextArea } from 'antd-mobile';
import styles from './index.less';

interface ChildrenProps {
  inputData: string,
  onDataChange?: ( newVal: string) => void;
}

const Children: FC<ChildrenProps> = props => {

  //父组件传过来的值和回调方法
  const {inputData, onDataChange } = props;
  //组件内部定义的state
  const [hasGetDefaultValue,setHasGetDefaultValue] = useState(false)
  const [defaultValue,setDefaultValue] = useState('')
  const [inputFlag ,setInputFlag] = useState(false)

  useEffect(()=>{
    if (inputData) {
        if(!hasGetDefaultValue){
            //只在第一次时候渲染该初始值,之后的value值由textarea非受控组件内部自己管理,这样可以避免 value值更新时整个组件重新渲染 导致onCompositionEnd事件未调用。
            setDefaultValue(inputData)
        }
        setHasGetDefaultValue(true);
    }
  },[inputData]);

  function onValueChange(value: string) {
    console.log('onValueChange', value)
    if (!inputFlag) {
      if (onDataChange) {
        onDataChange( value );
      }
    }
  }

  return (
    <div className={styles.childrenWrapper}>
      <div className={styles.inputWrapper}>
        {
          hasGetDefaultValue && 
           <TextArea
              style={{
                '--font-size': '14px',
              }}
              maxLength={300}
              showCount={true}
              autoSize={{
                minRows: 4,
                maxRows: 20
              }}
              defaultValue={defaultValue}
              onChange={onValueChange}
              onCompositionStart={ (e) => {
                console.log('onCompositionStart', e.target.value)
                setInputFlag(true)
              }}
              onCompositionEnd={ (e) => {
                console.log('onCompositionEnd', e.target.value)
                setInputFlag(false)
                if(onDataChange) {
                  onDataChange( e.target.value );
                }
              }}
              placeholder={'请输入'}
            />
        }
      </div>
    </div>
  );

};

export default Children;
//父组件Father代码:
import React, { FC, useState } from 'react';
import Children from './Children'
import styles from './index.less';
import classNames from 'classnames';

const Father: FC<FatherProps> = props => {

  const [inputData, setInputData] = useState('初始值')
  
  function renderContent() {
    return (
      <Children
        inputData={inputData}
        onDataChange={(newVal)=> {
          //更新父组件值,子组件重新渲染
          setInputData(newVal)
        }}
      />
    );
  }

  return (
    <div className={styles.FatherWrapper}>
      <div className={styles.fatherContent}>
        {renderContent()}
      </div>
    </div>
  );
};

export default Father;

效果:

ios 汉字根据拼音分组和排序 iphone汉字和拼音一起显示_ios_02

ios 汉字根据拼音分组和排序 iphone汉字和拼音一起显示_输入框_03

 

  可以看到,默认初始值为“初始值”,中文输入“一”时候,打第一个拼音“y”时会调用onCompositionStart事件,此时输入框的值为“初始值”。同时也会触发onValueChange事件,检测到输入框值变化,新值为:“初始值y”。

再输入后续拼音直到选中 所选汉字时,触发onCompositionEnd事件,通过e.target.value拿到真实输入内容,即“初始值一”。

 

注:最初的错误代码:如果通过value的方式给它赋值,即直接将父组件传过来的data赋值给input组件,代码如下:

import React, { FC, useContext, useEffect, useState } from 'react';
import { TextArea } from 'antd-mobile';
import styles from './index.less';

interface ChildrenProps {
  inputData: string,
  onDataChange?: ( newVal: string) => void;
}

const Children: FC<ChildrenProps> = props => {

  const {inputData, onDataChange } = props;

  const [inputFlag ,setInputFlag] = useState(false)


  function onValueChange(value: string) {
    console.log('onValueChange', value)
    if (!inputFlag) {
      if (onDataChange) {
        onDataChange( value );
      }
    }
  }

  return (
    <div className={styles.childrenWrapper}>
      <div className={styles.inputWrapper}>
        {
           <TextArea
              style={{
                '--font-size': '14px',
              }}
              maxLength={300}
              showCount={true}
              autoSize={{
                minRows: 4,
                maxRows: 20
              }}
              value={inputData}
              onChange={onValueChange}
              onCompositionStart={ (e) => {
                console.log('onCompositionStart', e.target.value)
                setInputFlag(true)
              }}
              onCompositionEnd={ (e) => {
                console.log('onCompositionEnd', e.target.value)
                setInputFlag(false)
                if(onDataChange) {
                  onDataChange( e.target.value );
                }
              }}
              placeholder={'请输入'}
            />
        }
      </div>
    </div>
  );

};

export default Children;

此时会发现,无法输入中文字符,原因就是 在每输入一个中文拼音时,都会触发组件的重新渲染,导致还未执行后续的onCompositionEnd事件,该组件就被重新渲染了。因此没有来得及走onDataChange方法。导致输入框内容始终为“初始值”三个字。

ios 汉字根据拼音分组和排序 iphone汉字和拼音一起显示_赋值_04