1.問(wèn)題描述展示
示例代碼所做限制為不允許輸入字母d,其他限制規(guī)則可以根據(jù)需求自己調(diào)整,使用React編寫(xiě),其他框架或原生均可根據(jù)該代碼理解原理進(jìn)行轉(zhuǎn)變,特意使用了中文鍵盤(pán)可以看到輸入框下面白色框閃出,就是我按下了鍵盤(pán)d鍵
1) gif圖示例

2)代碼
import { useState } from "react"
export default function () {
const [value, setValue] = useState(1234)
const onChange = (e) => {
const inputValue = e.nativeEvent.target.value
const value = inputValue.replace('d', '')
setValue(value)
}
return <div style={{marginTop:200}}>
<h3>焦點(diǎn)修改實(shí)例:不允許輸入字母 d 無(wú)處理情況</h3>
<input value={value} onChange={onChange}></input>
</div>
}
3)原因分析
假設(shè)目前輸入值為1234,光標(biāo)移動(dòng)到2后面并輸入字母d ,此時(shí)輸入框得到的輸入值為12d34,而我們通過(guò)代碼將本來(lái)應(yīng)該是12d34的值修改為了1234,也就導(dǎo)致了光標(biāo)定位到了最后一位
之所以這樣處理,猜想大概是因?yàn)橥ㄟ^(guò)代碼進(jìn)行值的修改的時(shí)候存在多種情況比如
a. 1234 => 12456 位數(shù)增多
b. 1234 => 12 位數(shù)變少
c. 1234 => 5678 值全變化
瀏覽器實(shí)現(xiàn)時(shí)為了統(tǒng)一這種情況 只要當(dāng)瀏覽器輸入框獲取的輸入值與設(shè)置的值不等的時(shí)候都重置光標(biāo)位置
react使用value對(duì)輸入框輸入值做控制的流程為: (以輸入框已經(jīng)輸入1234 光標(biāo)移動(dòng)到2后面輸入d為例)
1.按下d鍵
2.輸入框獲得值12d34,但是此時(shí)輸入框的值受value控制,onChange事件中如果不對(duì)value做修改則不會(huì)改變輸入框?qū)嶋H展示值 (也就是說(shuō)12d34瀏覽器輸入框內(nèi)部已經(jīng)獲取到的值為12d34,只是頁(yè)面并未變化,頁(yè)面變化由input上的value控制)
3.輸入框onChang事件觸發(fā),此時(shí)代碼處理,會(huì)將輸入框獲取值由本應(yīng)該設(shè)置為12d34,修改為了1234,光標(biāo)重置到最后面 (即輸入框內(nèi)部獲取的值如果不等于value值就會(huì)光標(biāo)重置到最后面)
2.解決方案
1)宏任務(wù)延時(shí)處理
原理為通過(guò)input.setSelectionRange改變光標(biāo)位置,具體api細(xì)節(jié)自行百度參考文檔
import { useState } from "react"
export default function () {
const [value1, setValue1] = useState(1234)
const onChange1 = (e) => {
const start = e.target.selectionStart
const inputValue = e.nativeEvent.target.value
const value = inputValue.replace('d', '')
const input = e.nativeEvent.target
setValue1(value)
setTimeout(() => {
inputValue.includes('d') && input.setSelectionRange(start - 1, start - 1)
}, 0)
}
return <div style={{marginTop:200}}>
<h3>焦點(diǎn)修改實(shí)例:不允許輸入字母 d 宏任務(wù)處理</h3>
<input value={value1} onChange={onChange1}></input>
</div>
}

雖然能夠讓光標(biāo)位置正確,但是多次輸入的時(shí)候存在光標(biāo)從最后面移動(dòng)到原位置的情況
2) 微任務(wù)處理(關(guān)于微任務(wù)宏任務(wù)可以自行百度了解)
import { useState } from "react"
export default function () {
const [value2, setValue2] = useState(1234)
const onChange2 = (e) => {
const start = e.target.selectionStart
const inputValue = e.nativeEvent.target.value
const value = inputValue.replace('d', '')
const input = e.nativeEvent.target
setValue2(value)
new Promise(res => {
res()
// promist.then的回調(diào)函數(shù)屬于微任務(wù)
}).then(() => {
inputValue.includes('d') && input.setSelectionRange(start - 1, start - 1)
})
}
return <div style={{marginTop:200}}>
<h3>焦點(diǎn)修改實(shí)例:不允許輸入字母 d 微任務(wù)處理</h3>
<input value={value2} onChange={onChange2}></input>
</div>
}

3.總結(jié)
輸入框onChange修改value值導(dǎo)致的光標(biāo)定位到最后的時(shí)機(jī)在微任務(wù)和宏任務(wù)的前面,
即 順序?yàn)?1.重置光標(biāo) => 2.微任務(wù) =>(瀏覽器內(nèi)部重置光標(biāo))界面重新渲染 => 3.宏任務(wù)
其中使用宏任務(wù)二者執(zhí)行間隔時(shí)間跨度大,導(dǎo)致重置光標(biāo)界面已經(jīng)發(fā)生變化,宏任務(wù)執(zhí)行恢復(fù)光標(biāo),導(dǎo)致看到光標(biāo)從最后移動(dòng)到代碼設(shè)置的位置(即:瀏覽器執(zhí)行恢復(fù)光標(biāo) =>界面重新渲染 ,光標(biāo)移動(dòng)到最后 => 代碼設(shè)置光標(biāo)位置 => 瀏覽器重新渲染,恢復(fù)到代碼設(shè)置的位置)
微任務(wù)則縮小了間隔或者說(shuō)順序 (即:瀏覽器執(zhí)行恢復(fù)光標(biāo) => 代碼設(shè)置光標(biāo)位置 => 瀏覽器重新渲染,設(shè)置為代碼設(shè)置的位置)
但是該代碼僅僅處理了簡(jiǎn)單的情況即每次只輸入一位的情況,還存在一些邊界情況未處理,例如直接復(fù)制dddd,調(diào)整輸入位置,粘貼就會(huì)導(dǎo)致光標(biāo)位置后移,此種情況就需要對(duì)原值與新輸入值做對(duì)比并進(jìn)行處理