フォーマットされたシーンの下で、React inputのカーソルの処理方法

4964 ワード

今日は数値フォーマットされたシーンについて、React inputカーソルの異常な表現と対応する処理方法についてお話しします.物語はissueから言えば、NumberFieldコンポーネントを使用して入力するとアンドロイドの下でカーソル位置が異常になり、連続入力で所望の結果が得られないというユーザーの反応がある.具体的にはどのような表現でしょうか.
図1アンドロイド下で望ましくない入力動作
Androidの携帯電話の下でフォーマットが発生するたびに、最後のカーソルが1つずれ、連続入力に問題が発生していることがわかります.この問題はPC ChromeにもiOSにも現れず,互換性の問題であると判定できる.しかし、この互換性の問題はどのように発生したのだろうか.
フォーマットを分析すると、上記のように18758を入力した場合、カード番号に対するフォーマットを行うため、元の値を「1875 8」に変更し、文字列の長さから5桁から6桁に変更した場合、カーソル位置が値が変化したときに最後の桁にジャンプしないと、スペースに留まり、1桁間違えたように見え、連続入力で問題が発生します.
入力ボックスのカーソルの変化挙動だけを見ると、これも異常な変化ではないようで、値の変化に応答せずに末尾にジャンプするだけです.しかし、iOSとPC Chromeの下でなぜ尾まで跳ねるのかという質問を引用した.
図2:同じコードがPC ChromeでAndroidと異なることを示している.
そこでネットで検索して、Reactのgithubの中でこのようなissueを見つけて、Cursor jumps to end of controlled input.ここでReactの主なメンテナの1人である@sophiebits(spicyj)は比較的正確な答えを与えた.
図3 sophiebits React controlled input value変化時のカーソル挙動の説明
もともとvalueの変化は非常に不確実性が大きいため、Reactは信頼性が高く汎用的な論理を使用してカーソルの位置を適切な位置に保存することができず、Reactは制御モードでの再レンダリング時にカーソルを最後の位置に移動します.これは少なくともPC ChromeとiOSの下でカーソルが最後まで動いた理由を説明していますが、アンドロイドの下ではなぜ同じ行為を示していないのか、私はまだ合理的な説明を見つけていません.
アンドロイドの表現をiOSと一致させる方法はありますか?また、読み返したり試したりして、最後に、再レンダリングのプロセスとinputのonChangeを前後の2つのtickに置くと、Androidのinputの表現が他のプラットフォームと一致することを発見しました.すなわち、カーソルが再レンダリング時に最後にジャンプするように表現され、概略コードは以下の通りです.
import React from 'React';
class Demo extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'xxx',
        };
    }
    
    handleChange(e) {
        const value = e.target.value;
        //    setTimeout   
        //   re-render   onChange      tick  
        setTimeout(() => {
            this.setState({
                value,
            });
        });
    }
    
    render() {
        return (
             { this.handleChange(e); }}  
            />
        );
    }
}

これでやっと表現の動きがアンドロイドやiOSで一致し、正常に入力された場合に期待通りに表現されますが、など、これでいいのでしょうか.これまでのReact issueで得られた結論から,どのような修正でもinputの最後にジャンプし,中間から修正するとどうなるかが分かる.
図4:中間編集時にまた問題が発生
上の図から分かるように、Reactはどのような修正でもカーソルを後ろに置くので、真ん中から修正すると、表現的にユーザーの予想に合わず、連続入力ができません.今回は両端の行動が一致していて、いずれも望ましくない状態です.
しかし、すべて正常ではなく、メリットもあり、プラットフォームに基づいてifelseを書く必要はなく、統一的に処理することができます.上記の議論から,Reactがカーソルの位置を保存していないのは,この挙動を支持する汎用的で信頼できるアルゴリズムがないためであることが分かった.これはinputの変化がスペースを増やしてフォーマットするか、文字をフィルタリングしたか、いくつかの条件が直接他の文字になったなど、予測できない変化行為をトリガーする可能性があるからです.しかし、デジタルフォーマットという単一シーンに細分化すると、カーソル位置の保存ロジックが簡単で明確になります.
ユーザ入力の過程では,2つのケースのみが存在し,末尾に追加と中間修正が行われる.エンディングに追加されたcaseでは、例えば18758^の場合、常に後ろに追加された状態であるため、カーソルを最後まで保持すればよい(すなわちデフォルト状態1875 8^).中間編集されたcaseでは、カーソルはエンディングではなく、187^5 8のように、7の後ろに8を追加すれば、理想的なアイコンは8の後(すなわち1878^58)に維持されるべきである.カーソルの位置が前回formatより前の状態を保存する必要があります.
論理ははっきりしていて、次はどのように実現するかの問題です.では、カーソルの位置をどのように探知し、修正しますか?これはinputの選択領域に関する属性に関連しており、マウスのドラッグやスクリーンの長押しなど、いくつかの方法でinputで1つの話の選択領域を完了することができることを知っています.そのため、カーソルの位置は実際には選択領域の開始点(selectionStart)と終了点(selectionEnd)によって決定されます.では、インスタンスコードは以下のように、この2つのプロパティを読み取り、保存、設定することで、実現したい目的を達成することができます.
class Demo extends React.Component {
    ...
    
    componentDidUpdate(prevProps) {
        const { value } = prevProps;
        const { inputSelection } = this;
        if (inputSelection) {
          //   didUpdate             
          //              ,               
          //          
          if (inputSelection.start < this.formatValue(value).length) {
            const input = this.input;
            input.selectionStart = inputSelection.start;
            input.selectionEnd = inputSelection.end;
            this.inputSelection = null;
          }
        }
    }
    
    handleChange(e) {
        //   onChange         
        if (this.input) {
          this.inputSelection = {
            start: this.input.selectionStart,
            end: this.input.selectionEnd,
          };
        }
        ...
    }
    
    render() {
        return (
             { this.input = c; }}
                value={this.state.value} 
                onChange={(e) => { this.handleChange(e); }}  
            />
        );
    }
}

これで、私たちはついに追加と中間編集の状況で私たちが望んでいる効果を実現しました.これは比較的小さな技術点であるが,React内部の処理ロジックやプラットフォームの相違性の問題に関連しているため,調査と解決はそれほど容易ではなく,類似の問題を抱えている学生に処理時に啓発されることを望んでいる.

文中の各エンドおよびブラウザ情報

  • Android
  • Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; CLT-AL00 Build/HUAWEICLT-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 
  • iOS
  • Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79
  • PC Chrome
  • Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36

    文書に含まれるコンポーネントライブラリ

  • SaltUI: https://github.com/salt-ui/sa...