溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

在數(shù)值格式化中React input的光標(biāo)怎么處理

發(fā)布時間:2021-06-12 09:27:33 來源:億速云 閱讀:360 作者:小新 欄目:web開發(fā)

這篇文章給大家分享的是有關(guān)在數(shù)值格式化中React input的光標(biāo)怎么處理的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

今天要來說的是有關(guān)于有數(shù)值格式化的場景中,React input 光標(biāo)的一些異常的表現(xiàn)和對應(yīng)的處理辦法。故事要從一個 issue 說起,有用戶反映在使用 NumberField 組件輸入時安卓下會出現(xiàn)光標(biāo)位置異常,導(dǎo)致連續(xù)輸入會達(dá)不到期望的結(jié)果。具體表現(xiàn)是什么樣的呢?

在數(shù)值格式化中React input的光標(biāo)怎么處理

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

可以看到,在安卓手機下每次格式化發(fā)生的時候,本來應(yīng)該一直在最后的光標(biāo)會錯格一位,導(dǎo)致連續(xù)輸入出現(xiàn)問題。而這個問題在 PC Chrome 和 iOS 上都沒有出現(xiàn),于是可以判定是一個兼容性問題。但這個兼容性問題是如何產(chǎn)生的呢?

分析一下格式化的話的過程,如上面的情況,輸入 18758 時,因為要做針對卡號的格式化,所以會將原有的值轉(zhuǎn)變?yōu)?"1875 8",從字符串長度上來看,從 5 位變成了 6 位,那么如果此時光標(biāo)位置沒有在值變化時跳到最后一位,則會停留在空格處,看起來就好像錯格了一位,連續(xù)輸入時就會有問題。

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

在數(shù)值格式化中React input的光標(biāo)怎么處理

圖2: 相同的代碼在 PC Chrome 下表現(xiàn)與安卓不同。

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

在數(shù)值格式化中React input的光標(biāo)怎么處理

圖3 sophiebits 關(guān)于 React controlled input value 變化時光標(biāo)行為的解釋

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

那有沒有辦法使安卓上的表現(xiàn)和 iOS 中一致呢?又是一陣翻閱和嘗試,最后發(fā)現(xiàn)如果將重新渲染的過程和 input 的 onChange 置于前后兩個 tick 中就可以使安卓中 input 的表現(xiàn)和其他平臺上表現(xiàn)一致,即表現(xiàn)為光標(biāo)在重新渲染時跳到最后,示意代碼如下。

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 
                value={this.state.value} 
                onChange={(e) => { this.handleChange(e); }}  
            />
        );
    }
}

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

在數(shù)值格式化中React input的光標(biāo)怎么處理

圖4:中間編輯時又會出現(xiàn)問題

從上面的圖里可以看出,因為 React 無論何種修改都會將光標(biāo)置尾,如果從中間進(jìn)行修改,那么表現(xiàn)地又會很不符合用戶預(yù)期,沒有辦法做到連續(xù)輸入。這回倒是兩端行為保持一致,都是不期望的狀態(tài)。。

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

在用戶輸入的過程中,只存在兩種情況,在結(jié)尾中追加和在中間修改。在結(jié)尾追加的 case 中,例如 18758^ 時,由于一直是在向后追加的狀態(tài),我們只要一直保持光標(biāo)在最后即可(即默認(rèn)狀態(tài) 1875 8^ ),在中間編輯的 case 下,光標(biāo)并不處于結(jié)尾,如 187^5 8,此時如果在 7 后面追加了一個 8,那么理想的圖標(biāo)應(yīng)該維持在 8 之后(即 1878^ 58),此時就應(yīng)該保存光標(biāo)的位置在上次 format 之前的狀態(tài)。

邏輯清楚了,接下來就是如何實現(xiàn)的問題了。那么如何探測和修改光標(biāo)位置呢?這就涉及了 input 中選區(qū)相關(guān)的屬性,我們知道我們可以通過一些方式(如鼠標(biāo)拖拽和長按屏幕等)在 input 中完成對一段話的選區(qū),因此光標(biāo)的位置其實是由選區(qū)的開始點(selectionStart)和結(jié)束點(selectionEnd)決定的。那么其實我們就可以通過讀取,儲存和設(shè)置這兩個屬性來達(dá)到我們想要實現(xiàn)的目的,實例代碼如下。

class Demo extends React.Component {
    ...
    
    componentDidUpdate(prevProps) {
        const { value } = prevProps;
        const { inputSelection } = this;
        if (inputSelection) {
          // 在 didUpdate 時根據(jù)情況恢復(fù)光標(biāo)的位置
          // 如果光標(biāo)的位置小于值的長度,那么可以判定屬于中間編輯的情況
          // 此時恢復(fù)光標(biāo)的位置
          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 時記錄光標(biāo)的位置
        if (this.input) {
          this.inputSelection = {
            start: this.input.selectionStart,
            end: this.input.selectionEnd,
          };
        }
        ...
    }
    
    render() {
        return (
            <input 
                ref={(c) => { this.input = c; }}
                value={this.state.value} 
                onChange={(e) => { this.handleChange(e); }}  
            />
        );
    }
}

至此,我們終于在追加和中間編輯的情況下都實現(xiàn)了我們想要的效果。這是一個比較小的技術(shù)點,但是由于里面涉及了一些 React 內(nèi)部的處理邏輯及平臺差異性問題,排查和解決起來并不是那么容易,希望可以給有類似問題的同學(xué)在處理時有所啟發(fā)。

在數(shù)值格式化中React input的光標(biāo)怎么處理

文中涉及的各端及瀏覽器信息

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

感謝各位的閱讀!關(guān)于“在數(shù)值格式化中React input的光標(biāo)怎么處理”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI