【Vue原理】VModel - 源碼版之input詳解

專註 Vue 源碼分享,為了方便大家理解,分為了白話版和 源碼版,白話版可以輕鬆理解工作原理和設計思想,源碼版可以更清楚內部操作和 Vue的美,喜歡我就關注我的公眾號,公眾號的文章,排版更好看

如果你覺得排版難看,請點擊下面鏈接 或者 關注公眾號

【Vue原理】VModel - 源碼版之input詳解?

mp.weixin.qq.com圖標

上一篇文章,我們大概講了所有表單元素的雙綁原理,但是仍然有兩個特殊的表單元素,是要多更多處理的,也不可能放在一篇文章說完,今天,我們說的是 input 的特殊處理的地方

而 input 有什麼特殊處理的地方呢?

1、預輸入延遲更新

2、range 類型的 input

3、v-model.lazy

4、v-model.trim、v-model-number

預輸入延遲更新

先來看看Vue 給 input 正常綁定的回調

input: function($event) {
if ($event.target.composing) return;
name = $event.target.value
}

看到我標紅的地方,這句話就是完成預輸入延遲更新的重點

當composing=true時,事件回調不會走到下面的更新操作,而 Vue 正式通過這個標誌位,判斷現在是否是預輸入而確定是否需要實時更新

首先,Vue 會為 input 或者 textarea 綁定以下事件

  • compositionstart
  • compositionend
  • change

開始講解這三個事件了

1、compositionstart

首先,compositionstart 會在 input 事件觸發之前 觸發

but!你打一些直接輸入的字元,是不會觸發 compositionstart 的,只會觸發 input

只有打預輸入的字元才會觸發,比如 輸入拼音,不行看圖

輸入普通字元

預輸入延遲更新下,輸入拼音

取消預輸入延遲更新,輸入拼音

看完上面的動圖,預輸入延遲更新什麼用,估計你心裡也有點逼數了吧?

為什麼要做預輸入延遲更新?

如果不做!在輸入拼音的時候,每打一個拼音字母都會觸發 input 事件,但是我們根本還沒往表單中寫入我們預想中的東西

而此時觸發 input 事件沒有任何意義,因為還不是我們要輸入的值,這是一個浪費的操作

剛好,compositionstart 在 input 之前觸發,而且只會預輸入才觸發

所以!就可以通過一個標誌位來控制 input 回調導致的更新就再好不過了!

怎麼做呢?看 compositionstart 回調源碼

function onCompositionStart(e) {
e.target.composing = true;
}

2、compositionend

在打完預輸入的字元之後,會觸發

在預輸入延遲更新中起什麼作用呢?

預輸入結束,肯定是設置 composing 為 false了,看源碼

function onCompositionEnd(e,eventname) {
if (!e.target.composing) { return }
e.target.composing = false;
trigger(e.target, input);
}

還會 手動觸發 input 事件去 執行更新操作哦

trigger 是什麼?也是很簡單的,值得收藏的源碼,不解釋了

function trigger(el, type) {
var e = document.createEvent(HTMLEvents);
e.initEvent(type, true, true);
el.dispatchEvent(e);
}

3、change

為了兼容Safari<10.2 等那些 不會觸發 compositionend 的瀏覽器(Vue自己注釋說的,我沒有測過),於是監聽 change事件,來代替 compositionend 的功能

change 的回調 和 compositionend 的回調是一樣的,因為只是一個備胎功能

4、 他們在哪裡開始綁定這些事件呢?

你應該必須知道,指令都是有生命鉤子函數的,而這幾個事件正是在 inserted 鉤子中進行綁定的

Vue 官方文檔說明 inserted

看下 inserted 鉤子函數

function inserted(el, binding, vnode, oldVnode) {

// isTextInputType判斷 input 是不是 text,number,password,search,email,tel,url其中一個
if (vnode.tag === textarea || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers;
// 如果設置 v-model.lazy,那麼不處理 預輸入的問題
if (!binding.modifiers.lazy) {
el.addEventListener(compositionstart, onCompositionStart);
el.addEventListener(compositionend, onCompositionEnd);
el.addEventListener(change, onCompositionEnd);
}
}
}

TIP

inserted 鉤子中,還處理了 select ,但是這裡是input的版塊,所以去掉了,放在下篇文章講

Range 類型 Input

為了兼容 IE,所以在解析的時候,先保存的是 __r 事件,後面開始綁定的時候,判斷瀏覽器而決定使用什麼事件

function genDefaultModel(
el, value, modifiers

){
var type = el.attrsMap.type;
var ref = modifiers || {};
var lazy = ref.lazy;

// 這裡省略了lazy 的判斷啦
var event = type === range ? "__r" :input;

code = "if($event.target.composing)return;"
+ value+"=$event.target.value";
addProp(el, value, ("(" + value + ")"));
addHandler(el, event, code, null, true);
}

看到我加藍加粗的地方,就是為 range 特別處理的地方,綁定 __r 事件

判斷瀏覽器,轉換事件的函數updateDOMListeners源碼

function updateDOMListeners(oldVnode, vnode) {

var on = vnode.data.on
if (isDef(on["__r"])) {
var event = isIE ? change: input;
on[event] = [].concat(on["__r"], on[event] || []);
delete on["__r"];
}
for (name in on) {
vnode.elm.addEventListener(name, on[name]);
}
}

v-model.lazy

當你的 v-model 設置了 lazy 的時候,會綁定 change 而不是 input,延時更新的意思

function genDefaultModel(
el, value, modifiers

){

var ref = modifiers || {};
var lazy = ref.lazy;

// 省略了 range 類型的判斷
var event = lazy ? change :input;
addHandler(el, event, code, null, true);
}

我們都知道,為了輸入實時響應,vue 默認為 input 等輸入類型的 表單 綁定 input 事件,讓 input

如果你設置延遲更新,就是相當於你改變了內容,然後失去焦點才觸發

v-model.trim、v-model.number

如果你給 v-model 設置了值過濾,像 trim 去掉首尾空格,number 把值變成數字

function genDefaultModel(
el, value, modifiers
){

var ref = modifiers || {};
var number = ref.number;
var trim = ref.trim;

// 去首尾空格
if (trim) {
valueExpression = "$event.target.value.trim()";
}

// 轉成數字
if (number) {
valueExpression = "_n(" + valueExpression + ")";
}

code = "if($event.target.composing)return;" +
value+"="+valueExpression;

addProp(el, value, ("(" + value + ")"));
addHandler(el, "input", code, null, true);

if (trim || number) {
addHandler(el, blur, $forceUpdate());
}

}

對於 trim 和 number,Vue 會對錶單值做處理,你可以看到源碼中

trim:值會調用 trim 方法

number:會調用 _n 轉換成數字方法

看下最終的回調

function($event){
if($event.target.composing)return;
name=_n($event.target.value);
}

function($event){
if($event.target.composing)return;
name=$event.target.value.trim();
}

這兩個鬼東西,還會額外綁定一個事件 blur

看下回調 $forceUpdate,這個函數作用是強制更新頁面

為什麼要更新頁面?給個動圖看好吧

我設置了 trim,然後輸入的時候,故意多加幾個空格,然後失去焦點(觸發設置的 blur),再點發現空格不見了。因為失去焦點之後被強制更新了一波

嗯,這就是 $forceUpdate 的作用,把頁面上的顯示值也過濾一遍

歷史分享

【Vue原理】Vue源碼閱讀總結大會 - 序

【Vue原理】學會調試Vue源碼

【Vue原理】響應式原理 - 白話版

【Vue原理】Props - 白話版

【Vue原理】月老Computed - 白話版

【Vue原理】Watch - 白話版

【Vue原理】Mixin - 白話版

【Vue原理】Methods - 源碼版

最後

希望你認真閱讀後,能豁然開朗。如果你懂了,我的目的也達到了,如果你還不懂,可以留言告訴我哦,百分百回復好吧

當然如果本文有任何描述不當的問題,歡迎後台聯繫本人

如果你能轉發一下,就更好啦,技術人交流 只用文章


推薦閱讀:

TAG:Vue.js | 前端開發 | 前端框架 |