摘要:具體表現(xiàn)是什么樣的呢圖安卓下不期望的輸入行為可以看到,在安卓手機(jī)下每次格式化發(fā)生的時(shí)候,本來(lái)應(yīng)該一直在最后的光標(biāo)會(huì)錯(cuò)格一位,導(dǎo)致連續(xù)輸入出現(xiàn)問(wèn)題。
今天要來(lái)說(shuō)的是有關(guān)于有數(shù)值格式化的場(chǎng)景中,React input 光標(biāo)的一些異常的表現(xiàn)和對(duì)應(yīng)的處理辦法。故事要從一個(gè) issue 說(shuō)起,有用戶(hù)反映在使用 NumberField 組件輸入時(shí)安卓下會(huì)出現(xiàn)光標(biāo)位置異常,導(dǎo)致連續(xù)輸入會(huì)達(dá)不到期望的結(jié)果。具體表現(xiàn)是什么樣的呢?
圖1 安卓下不期望的輸入行為
可以看到,在安卓手機(jī)下每次格式化發(fā)生的時(shí)候,本來(lái)應(yīng)該一直在最后的光標(biāo)會(huì)錯(cuò)格一位,導(dǎo)致連續(xù)輸入出現(xiàn)問(wèn)題。而這個(gè)問(wèn)題在 PC Chrome 和 iOS 上都沒(méi)有出現(xiàn),于是可以判定是一個(gè)兼容性問(wèn)題。但這個(gè)兼容性問(wèn)題是如何產(chǎn)生的呢?
分析一下格式化的話(huà)的過(guò)程,如上面的情況,輸入 18758 時(shí),因?yàn)橐鲠槍?duì)卡號(hào)的格式化,所以會(huì)將原有的值轉(zhuǎn)變?yōu)?"1875 8",從字符串長(zhǎng)度上來(lái)看,從 5 位變成了 6 位,那么如果此時(shí)光標(biāo)位置沒(méi)有在值變化時(shí)跳到最后一位,則會(huì)停留在空格處,看起來(lái)就好像錯(cuò)格了一位,連續(xù)輸入時(shí)就會(huì)有問(wèn)題。
單從輸入框的光標(biāo)變化行為來(lái)看,這好像也不算是一種異常的變化,只是不響應(yīng)值的變化跳到尾部而已。但引申出來(lái)的問(wèn)題是為什么在 iOS 和 PC Chrome 下又會(huì)跳動(dòng)到尾部呢。
圖2: 相同的代碼在 PC Chrome 下表現(xiàn)與安卓不同。
于是去網(wǎng)上搜索,輾轉(zhuǎn)在 React 的 github 中找到這樣一個(gè) issue, Cursor jumps to end of controlled input。在這里 React 的主要維護(hù)者之一的 @sophiebits(spicyj) 給出了一個(gè)比較確切的答案。
圖3 sophiebits 關(guān)于 React controlled input value 變化時(shí)光標(biāo)行為的解釋
原來(lái)因?yàn)?value 的變化具有非常大的不確定性,因此 React 無(wú)法使用一種可靠且通用的邏輯去保存光標(biāo)的位置在一個(gè)合適的位置,因此 React 在受控模式下的重新渲染都會(huì)時(shí)光標(biāo)移動(dòng)到最后的位置。這個(gè)至少解釋了PC Chrome 和 iOS 下光標(biāo)跳動(dòng)到結(jié)尾的原因,但安卓下為什么沒(méi)有表現(xiàn)出同樣的行為到目前位置我還沒(méi)有找到合理的解釋。
那有沒(méi)有辦法使安卓上的表現(xiàn)和 iOS 中一致呢?又是一陣翻閱和嘗試,最后發(fā)現(xiàn)如果將重新渲染的過(guò)程和 input 的 onChange 置于前后兩個(gè) tick 中就可以使安卓中 input 的表現(xiàn)和其他平臺(tái)上表現(xiàn)一致,即表現(xiàn)為光標(biāo)在重新渲染時(shí)跳到最后,示意代碼如下。
import React from "React"; class Demo extends React.Component { constructor(props) { super(props); this.state = { value: "xxx", }; } handleChange(e) { const value = e.target.value; // 通過(guò) setTimeout 異步 // 使 re-render 和 onChange 處于兩個(gè) tick 中 setTimeout(() => { this.setState({ value, }); }); } render() { return ( { this.handleChange(e); }} /> ); } }
這樣終于使得表現(xiàn)的行為在安卓和 iOS 上表現(xiàn)一致,并且正常輸入的情況下表現(xiàn)得比較符合期望了,然而等等,這樣就可以了嗎?從之前的 React issue 中得出的結(jié)論可以看出,無(wú)論是如何的修改都會(huì)跳至 input 的結(jié)尾,這樣如果是從中間修改的話(huà)會(huì)變成什么樣?
圖4:中間編輯時(shí)又會(huì)出現(xiàn)問(wèn)題
從上面的圖里可以看出,因?yàn)?React 無(wú)論何種修改都會(huì)將光標(biāo)置尾,如果從中間進(jìn)行修改,那么表現(xiàn)地又會(huì)很不符合用戶(hù)預(yù)期,沒(méi)有辦法做到連續(xù)輸入。這回倒是兩端行為保持一致,都是不期望的狀態(tài)。。
但是都不正常也有好處,不需要根據(jù)平臺(tái)去寫(xiě)一些 ifelse,可以統(tǒng)一地去做處理。從上面的討論中我們可以知道 React 沒(méi)有保存光標(biāo)的位置是因?yàn)闆](méi)有一個(gè)通用并且可靠的算法去支撐這一行為。這是因?yàn)?input 的變化可能是增加空格做格式化,也可能是過(guò)濾過(guò)些字符,也可能是觸發(fā)某些條件直接變成了其他字符等各種無(wú)法預(yù)測(cè)的變化行為。但是細(xì)化到數(shù)字格式化這一單一場(chǎng)景時(shí),光標(biāo)位置的保存邏輯就變得簡(jiǎn)單和清晰的多了。
在用戶(hù)輸入的過(guò)程中,只存在兩種情況,在結(jié)尾中追加和在中間修改。在結(jié)尾追加的 case 中,例如 18758^ 時(shí),由于一直是在向后追加的狀態(tài),我們只要一直保持光標(biāo)在最后即可(即默認(rèn)狀態(tài) 1875 8^ ),在中間編輯的 case 下,光標(biāo)并不處于結(jié)尾,如 187^5 8,此時(shí)如果在 7 后面追加了一個(gè) 8,那么理想的圖標(biāo)應(yīng)該維持在 8 之后(即 1878^ 58),此時(shí)就應(yīng)該保存光標(biāo)的位置在上次 format 之前的狀態(tài)。
邏輯清楚了,接下來(lái)就是如何實(shí)現(xiàn)的問(wèn)題了。那么如何探測(cè)和修改光標(biāo)位置呢?這就涉及了 input 中選區(qū)相關(guān)的屬性,我們知道我們可以通過(guò)一些方式(如鼠標(biāo)拖拽和長(zhǎng)按屏幕等)在 input 中完成對(duì)一段話(huà)的選區(qū),因此光標(biāo)的位置其實(shí)是由選區(qū)的開(kāi)始點(diǎn)(selectionStart)和結(jié)束點(diǎn)(selectionEnd)決定的。那么其實(shí)我們就可以通過(guò)讀取,儲(chǔ)存和設(shè)置這兩個(gè)屬性來(lái)達(dá)到我們想要實(shí)現(xiàn)的目的,實(shí)例代碼如下。
class Demo extends React.Component { ... componentDidUpdate(prevProps) { const { value } = prevProps; const { inputSelection } = this; if (inputSelection) { // 在 didUpdate 時(shí)根據(jù)情況恢復(fù)光標(biāo)的位置 // 如果光標(biāo)的位置小于值的長(zhǎng)度,那么可以判定屬于中間編輯的情況 // 此時(shí)恢復(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 時(shí)記錄光標(biāo)的位置 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); }} /> ); } }
至此,我們終于在追加和中間編輯的情況下都實(shí)現(xiàn)了我們想要的效果。這是一個(gè)比較小的技術(shù)點(diǎn),但是由于里面涉及了一些 React 內(nèi)部的處理邏輯及平臺(tái)差異性問(wèn)題,排查和解決起來(lái)并不是那么容易,希望可以給有類(lèi)似問(wèn)題的同學(xué)在處理時(shí)有所啟發(fā)。
文中涉及的各端及瀏覽器信息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文中涉及的組件庫(kù)
SaltUI: https://github.com/salt-ui/sa...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98573.html
摘要:踩的大坑前言最近有個(gè)需求要將全平臺(tái)的交易密碼由原來(lái)的位復(fù)雜密碼改為位純數(shù)字交易密碼,涉及到非常多的業(yè)務(wù)場(chǎng)景,但修改起來(lái)也無(wú)非兩種設(shè)置交易密碼,使用交易密碼設(shè)置交易密碼普通長(zhǎng)條輸入框彈起數(shù)字鍵盤(pán)支持明暗文切換查看使用交易密碼顯示六個(gè)格子彈起 input ios 踩的大坑 前言:最近有個(gè)需求要將全平臺(tái)的交易密碼由原來(lái)的 6-16位 復(fù)雜密碼改為6位純數(shù)字交易密碼,涉及到非常多的業(yè)務(wù)場(chǎng)景,但...
摘要:背景最近我們微信讀書(shū)將寫(xiě)想法換成了基于的富文本編輯器,遇到了不少問(wèn)題,這里我將簡(jiǎn)單的介紹一下我們?cè)陂_(kāi)發(fā)過(guò)程中踩到的坑。 背景 最近我們微信讀書(shū)將寫(xiě)想法換成了基于webview的富文本編輯器,遇到了不少問(wèn)題,這里我將簡(jiǎn)單的介紹一下我們?cè)陂_(kāi)發(fā)過(guò)程中踩到的坑。 實(shí)現(xiàn)富文本編輯器有兩個(gè)基本思路: 基于native實(shí)現(xiàn):比如coretext或者textkit 基于uiwebview實(shí)現(xiàn) 第一...
摘要:我們來(lái)從設(shè)計(jì)思想上,和官方團(tuán)隊(duì)的回應(yīng)上,了解一下否決理由。此外,還有一個(gè)方法新的接口設(shè)計(jì)支持接收一個(gè)回調(diào)函數(shù),當(dāng)其子組件掛載時(shí),這個(gè)回調(diào)函數(shù)就會(huì)相應(yīng)觸發(fā)。 從 setState 那個(gè)眾所周知的小秘密說(shuō)起... 在 React 組件中,調(diào)用 this.setState() 是最基本的場(chǎng)景。這個(gè)方法描述了 state 的變化、觸發(fā)了組件 re-rendering。但是,也許看似平常的 th...
摘要:我們來(lái)從設(shè)計(jì)思想上,和官方團(tuán)隊(duì)的回應(yīng)上,了解一下否決理由。此外,還有一個(gè)方法新的接口設(shè)計(jì)支持接收一個(gè)回調(diào)函數(shù),當(dāng)其子組件掛載時(shí),這個(gè)回調(diào)函數(shù)就會(huì)相應(yīng)觸發(fā)。 從 setState 那個(gè)眾所周知的小秘密說(shuō)起... 在 React 組件中,調(diào)用 this.setState() 是最基本的場(chǎng)景。這個(gè)方法描述了 state 的變化、觸發(fā)了組件 re-rendering。但是,也許看似平常的 th...
摘要:由于我們的富文本輸入框比較簡(jiǎn)單,所以只需要處理兩類(lèi)數(shù)據(jù)即可,其一是普通的文本類(lèi)型數(shù)據(jù),包括表情其二則是圖片類(lèi)型數(shù)據(jù)。 最近折騰 Websocket,打算開(kāi)發(fā)一個(gè)聊天室應(yīng)用練練手。在應(yīng)用開(kāi)發(fā)的過(guò)程中發(fā)現(xiàn)可以插入 emoji ,粘貼圖片的富文本輸入框其實(shí)蘊(yùn)含著許多有趣的知識(shí),于是便打算記錄下來(lái)和大家分享。 倉(cāng)庫(kù)地址:chat-input-box預(yù)覽地址:https://codepen.io...
閱讀 3173·2023-04-26 00:49
閱讀 3814·2021-09-29 09:45
閱讀 1141·2019-08-29 18:47
閱讀 2804·2019-08-29 18:37
閱讀 2797·2019-08-29 16:37
閱讀 3356·2019-08-29 13:24
閱讀 1836·2019-08-27 10:56
閱讀 2413·2019-08-26 11:42