VueJS 與 Input Number 的兩三事

Walker Chen
parenting-tw
Published in
5 min readNov 28, 2019

一些我在開發過程中踩到的雷

Photo by Lalith T on Unsplash

用 VueJS 開發前端時,若要讓使用者自行輸入數值(數值必須是大於零的整數,且不能超過一個特定值),或是透過旁邊的按鈕加減數值,初步的想法大概是這樣:

<input type="number" v-model="quantity" :min="min" :max="max" />
<button @click="subQuantity"> - </button>
<button @click="addQuantity"> + </button>
<script>
export default {
data() {
return {
max: 99,
min: 1,
quantity: 1
}
},
methods: {
addQuantity() {
if (this.quantity < this.max) this.quantity++;
},
subQuantity() {
if (this.quantity > this.min) this.quantity--;
},
}
}
</script>

看似沒什麼問題,但實際測試時出現許多 bug。。。

一、數值突然變字串

使用自訂的按鈕加加減減,或透過鍵盤的上下鍵操作都能很快計算並修改數值;但只要一自行輸入,兩個數值就直接合併起來了,數值相加變成了字串相接 (囧)。

雖然將 <input> 的 type 設成 number 了,但 html 實際回傳的 value 依然是 string,若希望能夠自動轉換型態,可使用 v-modelModifiers

<input type="number" v-model.number="quantity" :min="min" :max="max" />

二、標籤內的 min 與 max 屬性只對鍵盤事件有用

<input> 標籤內雖然有設置 min 與 max 屬性,但這似乎只對鍵盤的上下鍵調整數值時有用,當按著上下鍵遞增或遞減數值時,會自動停在標籤設置的最大或最小值(自定義的按鈕因有設條件,因此也不會超過);但自行輸入若超過邊界則不會被限制住。

也許再加個 onchange 檢驗一下就可以了吧。

<input type="number" v-model.number="quantity" :min="min" :max="max" @change="verifyQuantity" />

三、數值可以輸入 e 跟負號是常識?

e 是數學符號、數值可以輸入負數,可以輸入這兩個符號是想當然的事情,也不是第一天知道;但像這種『12e43』、『432–221e213』、『ee2e-』、『 −−23−−323−−』也可以輸入真是頭一次發現。。。

反正在我們的 case 中也不會輸入這兩個符號,索性將它們全鎖了吧!問題是。。。怎麼做呢?

四、標籤內的 pattern 屬性只在表單中有用

看到 <input> 標籤內有 pattern 屬性時開心了一下,先前寫不少正規表達式,這點要求可難不倒我。但是後來發現這個 pattern 屬性只有在送表單時才會被觸發 Orz

五、連 onchange 也不行?!

既然 pattern 的方式不行,那就在 onchange 事件中處理吧;就在寫好判斷繼續測試時,竟發現連onchange 也不會被觸發 (囧)

把輸入的數值實際印出來觀察,發現上面那些亂打的數值都會被前面所講的 v-modelModifiers 轉為空字串;既然都是一樣的空字串,自然也不會觸發 onchange 事件了 (苦笑)

六、NaN 不等於 NaN

在把字串透過 parseInt() 強行轉成整數時,會得到 NaNNaN 的型別是 number,所以透過型別區分 NaN 跟一般數值是行不通的。有趣的是透過 value === NaN 來判斷時,即使 valueNaN ,也會得到 false 的結果;但用 if (! value) 來判斷時則可以得到 true

最後是用 computed 的雙向綁定方式來處理這個問題

<input type="number" v-model.number="verifiedQuantity" :min="min" :max="max" />
<button @click="subQuantity"> - </button>
<button @click="addQuantity"> + </button>
<script>
export default {
data() {
min: 1,
max: 99,
quantity: 1
}
computed: {
verifiedQuantity: {
get() {
return this.quantity;
},
set(value) {
if (! value || typeof value === 'string') {
return;
} else if (value >= this.min && value <= this.max) {
this.quantity = value;
}
}
}
},
methods: {
addQuantity() {
this.verifiedQuantity++;
},
subQuantity() {
this.verifiedQuantity--;
},
}
}
</script>

--

--