[vue] 表單輸入格式化,中文輸入法異常

張躍遷發表於2018-05-30

v-model 是 vue.js 提供的語法糖,根據不同的表單控制元件監聽不同的事件,實現對錶單控制元件的資料雙向繫結。當控制元件是<input>輸入框時,v-model 監聽其 input 事件。如下所示,這兩種寫法有什麼區別嗎?

<input :value="name" @input="name = $event.target.value">
<input v-model="name">
複製程式碼

輸入中文格式化問題

表單輸入常見需求:對<input>控制元件輸入的內容進行格式化,譬如:轉成大寫字母。如果輸入的值包含中文,格式化就會引起輸入法異常。現在使用上述兩種寫法來實現,示例如下:

使用 :value, @input - 輸入法異常,如下圖所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

使用 v-model 指令 - 輸入法正常,如下圖所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

上面的問題,可以看出 v-model 不只是給變數賦值,那麼,它還做了些什麼呢?

v-model 原始碼分析

本文參考的 vue.js 原始碼是 2.5.16

翻看 v-model 原始碼,可以看到 v-model 關注的仍然是 input 事件
[vue] 表單輸入格式化,中文輸入法異常

input 事件繫結的回撥程式碼處理,如下:
[vue] 表單輸入格式化,中文輸入法異常
這裡可以看到,v-model 判斷了 composing 屬性,當輸入法組合沒有結束的時候,直接返回,並沒有賦值。

composing 屬性並不是標準 dom 元素屬性,那它是怎麼來的呢?
[vue] 表單輸入格式化,中文輸入法異常
這裡可以看出,composing 屬性是 vue.js 新增到 dom 節點上的。

那麼,是什麼地方呼叫了這2個函式呢?可以看到,在插入dom節點時,vue.js 監聽了 compositionstart / compositionend 事件
[vue] 表單輸入格式化,中文輸入法異常
compositionstart / compositionend 這2個是 dom 原生事件,瀏覽器相容性問題可以查閱 MDN 說明:

原始碼中還發現,在是否重新整理 dom 屬性值時,也用到了 composing 屬性:
[vue] 表單輸入格式化,中文輸入法異常
這裡可以看出,在輸入法組合過程中,vue.js 變數值的更新亦不會同步到 dom元素的 value 屬性。

綜上所述:
  • v-model 實際上是監聽了<input>控制元件的 input、compositionstart、compositionend 三個事件,在輸入法組合過程中就直接返回不賦值
  • v-model 指令設定了變數 composing,此標識還用於判斷是否更新 dom元素的 value 屬性

v-model 輸入中文觸發的事件

從上面原始碼分析可知,v-model 繫結<input>輸入中文時,實際觸發的事件如下:
[vue] 表單輸入格式化,中文輸入法異常
[vue] 表單輸入格式化,中文輸入法異常

compositionstart => 3個 input => compositionend 事件,這些都是<input>控制元件觸發的。最後一個 input 事件,是原始碼裡面看到的 onCompositionEnd 回撥裡面 vue.js 觸發的。

1、compositionstart事件
修改dom物件的composing屬性為 true

2、3個input事件
由於dom物件的composing屬性為true,不會賦值,直接返回。

3、compositioinend事件
修改dom物件的composing屬性為 false

4、vue.js觸發的 input事件
由於dom物件的composing屬性為false,賦值,修改相應變數的值。

格式化問題原因分析

再次看下文章開頭的示例,如果使用 v-model 指令實現資料雙向繫結,就不會出現輸入法異常,如下圖所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

由前面的分析可知:
  • v-model 指令設定了變數 composing,雖然程式碼在 format 函式裡更改了 this.name 的值,但此時 composing 標識阻止了將 this.name 的值同步給 input 元素的 value 屬性
  • 如果你感興趣的話,可以修改 vue.js 的原始碼,將 shouldUpdateValue 函式裡對 composing 屬性的判斷去掉,可以看到輸入法又跟之前一樣異常了

格式化問題 bugfix

再次看下文章開頭的示例,如果使用 :value, @input 實現資料雙向繫結,有三個方案可以解決中文輸入異常的問題。

方案一:改用 v-model
v-model 已經處理了中文輸入法組合的問題,直接用就好啦,如下所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

方案二:監聽 change 事件
等輸入結束失去焦點以後,再呼叫格式化方法,如下所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

方案三:監聽 input 事件,同時判斷輸入法組合過程
在輸入法組合過程中,不進行格式化,如下所示,也可以線上嘗試
[vue] 表單輸入格式化,中文輸入法異常

相關文章