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

寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】vue

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧node

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

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

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

一、預輸入延遲更新學習

二、range 類型的 inputui

三、v-model.lazyurl

四、v-model.trim、v-model-numberspa


預輸入延遲更新

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

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

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

當composing=true時,事件回調不會走到下面的更新操做,而 Vue 正式經過這個標誌位,判斷如今是不是預輸入而肯定是否須要實時更新

首先,Vue 會爲 input 或者 textarea 綁定如下事件

compositionstart compositionend change 開始講解這三個事件了

一、compositionstart

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

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

只有打預輸入的字符纔會觸發,好比 輸入拼音,不行看圖

輸入普通字符

image

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

image

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

image

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

爲何要作預輸入延遲更新?

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

而此時觸發 input 事件沒有任何意義,由於還不是咱們要輸入的值,這是一個浪費的操做

恰好,compositionstart 在 input 以前觸發,並且只會預輸入才觸發

因此!就能夠經過一個標誌位來控制 input 回調致使的更新就再好不過了!

怎麼作呢?看 compositionstart 回調源碼

function onCompositionStart(e) {
    e.target.composing = true;
}
複製代碼

二、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);
}
複製代碼

三、change

爲了兼容Safari<10.2 等那些 不會觸發 compositionend 的瀏覽器(Vue本身註釋說的,我沒有測過),因而監聽 change事件,來代替 compositionend 的功能

change 的回調 和 compositionend 的回調是同樣的,由於只是一個備胎功能

四、 他們在哪裏開始綁定這些事件呢?

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

Vue 官方文檔說明 inserted

image

看下 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 轉換成數字方法

看下最終的回調

image

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

image

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

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

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

爲何要更新頁面?給個動圖看好吧

image

image

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

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

公衆號