首頁  >  文章  >  web前端  >  在數值格式化中React input 的遊標的處理辦法(詳細)

在數值格式化中React input 的遊標的處理辦法(詳細)

不言
不言轉載
2018-10-22 17:42:274978瀏覽

這篇文章帶給大家的內容是關於在數值格式化中React input 的遊標的處理辦法(詳細),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

今天要來說的是有關於有數值格式化的場景中,React input 遊標的一些異常的表現和對應的處理辦法。故事要從一個 issue 說起,有使用者反映在使用 NumberField 元件輸入時安卓下會出現遊標位置異常,導致連續輸入會達不到期望的結果。具體表現是什麼樣的呢?

在數值格式化中React input 的遊標的處理辦法(詳細)

圖1 安卓下不期望的輸入行為

可以看到,在安卓手機下每次格式化發生的時候,本來應該一直在最後的遊標會錯格一位,導致連續輸入出現問題。而這個問題在 PC Chrome 和 iOS 都沒有出現,於是可以判定是相容性問題。但這個相容性問題是如何產生的呢?

分析一下格式化的話的過程,如上面的情況,輸入18758 時,因為要做針對卡號的格式化,所以會將原有的值轉變為"1875 8",從字串長度上來看,從5 位變成了6 位,那麼如果此時間標位置沒有在值變化時跳到最後一位,則會停留在空格處,看起來就好像錯格了一位,連續輸入時就會有問題。

單從輸入框的遊標變化行為來看,這好像也不算是一種異常的變化,只是不響應值的變化跳到尾部而已。但引申出來的問題是為什麼在 iOS 和 PC Chrome 下又會跳動到尾部。

在數值格式化中React input 的遊標的處理辦法(詳細)

圖2: 相同的程式碼在 PC Chrome 下表現與安卓不同。

於是去網上搜索,輾轉在 React 的 github 中找到這樣一個 issue, Cursor jumps to end of controlled input。這裡 React 的主要維護者之一的 @sophiebits(spicyj) 給出了一個比較確切的答案。

在數值格式化中React input 的遊標的處理辦法(詳細)

圖3 sophiebits 關於React controlled input value 變化時間標記行為的解釋

原來因為value的變化具有非常大的不確定性,因此React 無法使用一種可靠且通用的邏輯去保存遊標的位置在一個合適的位置,因此React 在受控模式下的重新渲染都會時間標移動到最後的位置。這至少解釋了PC Chrome 和 iOS 下遊標跳動到結尾的原因,但安卓下為什麼沒有表現出同樣的行為到目前位置我還沒有找到合理的解釋。

那有沒有辦法讓安卓上的表現和 iOS 中一致呢?又是一陣翻閱和嘗試,最後發現如果將重新渲染的過程和input 的onChange 置於前後兩個tick 中就可以使安卓中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 (
            <input> { this.handleChange(e); }}  
            />
        );
    }
}

這樣終於使得​​表現的行為在安卓和 iOS 上表現一致,並且正常輸入的情況下表現得比較符合期望了,然而等等,這樣就可以了嗎?從先前的React issue 中得出的結論可以看出,無論是如何的修改都會跳至input 的結尾,這樣如果是從中間修改的話會變成什麼樣?

在數值格式化中React input 的遊標的處理辦法(詳細)

圖4:中間編輯時又會出現問題

#從上面的圖可以看出,因為React 無論何種修改都會將遊標置尾,如果從中間進行修改,那麼表現地又會很不符合使用者預期,沒有辦法做到連續輸入。這回倒是兩端行為一致,都是不期望的狀態。 。

但是都不正常也有好處,不需要根據平台去寫一些 ifelse,可以統一地去做處理。從上面的討論中我們可以知道 React 沒有保存遊標的位置是因為沒有一個通用且可靠的演算法來支撐這一行為。這是因為 input 的變化可能是增加空格做格式化,也可能是過濾過些字符,也可能是觸發某些條件直接變成了其他字符等各種無法預測的變化行為。但是當細化到數位格式化這單一場景時,遊標位置的保存邏輯就變得簡單和清晰的多了。

在用户输入的过程中,只存在两种情况,在结尾中追加和在中间修改。在结尾追加的 case 中,例如 18758^ 时,由于一直是在向后追加的状态,我们只要一直保持光标在最后即可(即默认状态 1875 8^ ),在中间编辑的 case 下,光标并不处于结尾,如 187^5 8,此时如果在 7 后面追加了一个 8,那么理想的图标应该维持在 8 之后(即 1878^ 58),此时就应该保存光标的位置在上次 format 之前的状态。

逻辑清楚了,接下来就是如何实现的问题了。那么如何探测和修改光标位置呢?这就涉及了 input 中选区相关的属性,我们知道我们可以通过一些方式(如鼠标拖拽和长按屏幕等)在 input 中完成对一段话的选区,因此光标的位置其实是由选区的开始点(selectionStart)和结束点(selectionEnd)决定的。那么其实我们就可以通过读取,储存和设置这两个属性来达到我们想要实现的目的,实例代码如下。

class Demo extends React.Component {
    ...
    
    componentDidUpdate(prevProps) {
        const { value } = prevProps;
        const { inputSelection } = this;
        if (inputSelection) {
          // 在 didUpdate 时根据情况恢复光标的位置
          // 如果光标的位置小于值的长度,那么可以判定属于中间编辑的情况
          // 此时恢复光标的位置
          if (inputSelection.start  { this.input = c; }}
                value={this.state.value} 
                onChange={(e) => { this.handleChange(e); }}  
            />
        );
    }
}

至此,我们终于在追加和中间编辑的情况下都实现了我们想要的效果。这是一个比较小的技术点,但是由于里面涉及了一些 React 内部的处理逻辑及平台差异性问题,排查和解决起来并不是那么容易,希望可以给有类似问题的同学在处理时有所启发。

在數值格式化中React input 的遊標的處理辦法(詳細)

文中涉及的各端及浏览器信息

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...

以上是在數值格式化中React input 的遊標的處理辦法(詳細)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除