摘要:首先,兄弟,容我先說幾句涉及源碼很多,篇幅很長,我都已經分了上下三篇了,依然這么長,但是其實內容都差不多一樣,但是我還是毫無保留地給你了。
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】VModel - 源碼版 之 表單元素綁定流程
今天講解 v-model 的源碼版。首先,兄弟,容我先說幾句
v-model 涉及源碼很多,篇幅很長,我都已經分了上下 三篇了,依然這么長,但是其實內容都差不多一樣,但是我還是毫無保留地給你了。你知道我這篇文章寫了多久,一個多星期啊,不是研究多久啊,是寫啊寫啊,不停地修修改改,一直在想如何才能講明白
如果你做好了十足的學習準備,會對你事半功倍,如果你只是看看,請看白話版吧,不然估計會越看越煩.....
如果你看過白話版,估計你會了解今天內容的大概,也能很快就入戲
今天講解不同表單元素的Vue是如何處理的,表單元素有
input、textarea、select、checkbox、radio 五大種
所以,我們把每個表單元素當做一個模塊,然后每個模塊解決三個問題的流程,來開始我們今天的表演
1、v-model 如何綁定表單值
2、v-model 如何綁定事件
4、v-model 如何雙向更新
TIP
下面所有涉及到的源碼,為了方便理解,都是簡化過的,因為源碼太長,所以只保留主要思想,去掉了很多兼容處理以及錯誤處理
v-model 指令的處理我們現在假設模板的解析已經到了 解析 v-model 的部分....
Vue 會調用 model 方法 來解析 v-model ,這個方法里面,針對不同的表單元素,再調用不同的專屬方法進行深度解析
function model(el, dir) { var value = dir.value; var tag = el.tag; var type = el.attrsMap.type; if (tag === "select") { genSelect(el, value); } else if (tag === "input" && type === "checkbox") { genCheckboxModel(el, value); } else if (tag === "input" && type === "radio") { genRadioModel(el, value); } else if (tag === "input" || tag === "textarea") { genDefaultModel(el, value); } }
你也看到了,上面每種表單元素都會使用一個方法來特殊照顧,不過這些方法,作用大致一樣
1、給表單元素設置綁定值
2、給表單元素設置事件及回調
所以這里,我們把方法的都設計到的方法以及流程說一下
插播上面的el 是什么?
el 是 ast,而我的理解就是解析模板后,用樹結構來表示某個dom節點,這里先不用深究,你就只要知道他是保存解析模板后所有的數據,包括你綁定的事件,綁定的指令,綁定的屬性等等,一張圖看下
下面所有的處理都是以 el 為基礎的
表單元素設置綁定值
什么叫設置綁定值?
首先,比如你給表單元素設置 v-model ="name",name 是 內部數據吧,所以要把 name 和 表單元素 兩個緊緊綁定起來,方便后面進行雙向更新
這里講的是每個表單元素綁定值的流程
他們都會調用 addProp 去保存綁定的屬性 然后 綁定屬性,流程一樣,所以提出來講,但是具體綁定什么屬性,每種元素都不盡相同,在下面表單元素模塊會詳解
1、調用 addProp,把 value 添加進 el.props
function addProp(el, name, value) { (el.props || (el.props = [])).push({ name: name, value: value }); }
2、接下來的解析,el.props 會拼接成進字符串 domProps
function genData$2(el, state) { var data = "{"; if (el.props) { data += "domProps:{" + (genProps(el.props)) + "},"; } data = data.replace(/,$/, "") + "}"; return data }
3、在插入 dom 之前,調用 updateDOMProps,把 上面保存的 domProps 遍歷賦值到 dom 上
function updateDOMProps(oldVnode, vnode) { var props = vnode.data.domProps || {}; for (key in props) { cur = props[key]; if (key === "value") { elm._value = cur; elm.value = strCur; } else { elm[key] = cur; } } }
表單元素設置事件以及回調
這里講的是每個表單元素綁定事件的流程
1、拼接事件
每種元素拼接事件都不一樣,在下面表單元素模塊會詳解
2、保存事件名和拼接好的回調
每個元素的 event 事件 和 拼接的回調是不一樣,但是他們保存的流程都是一樣的,都會調用下面的方法,addHandler 去保存事件
下面 el 是dom 元素,event 是事件名,code 是拼接的回調
function addHandler(el, name, value) { var events = el.events || (el.events = {}); var newHandler = { value: value.trim() }; var handlers = events[name]; if (Array.isArray(handlers)) { important ? handlers.unshift(newHandler) : handlers.push(newHandler); } else if (handlers) { events[name] = important ? [newHandler, handlers] : [handlers, newHandler]; } else { events[name] = newHandler; } }
3、完善拼接回調
function genData$2(el) { var data = "{"; if (el.events) { data += (genHandlers(el.events, false)) + ","; } data = data.replace(/,$/, "") + "}"; return data }
genHandlers遍歷 el.event ,每一項的回調最外層包上一層 function 字符串,并把 所有事件 逐個拼接成 on 字符串
function genHandlers(events) { var res = "on:{"; for (var name in events) { res += """ + name + "":" + ("function($event){" + (events[name].value) + ";}") + ","; } return res.slice(0, -1) + "}" }
轉接的 初始數據和結果 像下面這樣
4、綁定事件
在插入 dom 之前
會調用到 updateDOMListeners,把 上面保存到 on 的 所有事件, 遍歷綁定到 dom 上
updateDOMListeners 其實兜兜轉轉了很多方法 來處理,為了方便理解,已經非常簡化,但是意思是不變的
尤大:臥槽,我寫幾百行,你濃縮成5行,你這是要向全國人民謝罪的啊
function updateDOMListeners(vnode) { for (name in vnode.data.on) { vnode.elm.addEventListener(event, handler); } }
下面所有例子使用這個vue實例,所有綁定 v-model 我都用 name
Input、Textarea喲喲,看過 model ,就知道 這兩種元素是使用 genDefaultModel 處理的
function genDefaultModel(el, value, modifiers) { var code = "if($event.target.composing)return;" + value + "=$event.target.value;"; addProp(el, "value", ("(" + value + ")")); addHandler(el, "input", code, null, true); }
綁定值
看了上面的函數,你就知道啦,input 和 textarea 調用 addProp 綁定的是 value
拼接事件
其實這里精煉就一句話,比 jio 簡單
name = $event.target.value
但是呢!input 這里其實是很復雜的,比如兼容 range 啦,預輸入延遲更新啦 等等,但是現在我們不說這些,放到下篇來講
然后,你能看到,input 和 textarea 一般綁定的是 input 事件,但是也有其他的處理,下篇講啦
編譯后的渲染 render 函數
with(this) { return _c("input", { directives: [{ name: "model", rawName: "v-model", value: (name), expression: "name" }], attrs: { "type": "text" }, domProps: { "value": (name) }, on: { "input": function($event) { if ($event.target.composing) return; name = $event.target.value; } } })] }
雙向更新
我們可以看到上面的 render 執行的時候,從實例讀取了 name,name 收集到 本組件 watcher
1、內部變化,通知更新 watcher,render 重新執行,獲取新的 name,綁定到 dom 元素屬性 value
2、外部變化,看上面的回調事件,可以知道直接把 $event.target.value 賦值給 內部值name
Select來看看 處理 select 的 genSelect 方法
function genSelect(el, value, modifiers) { var selectedVal = ` Array.prototype.filter.call($event.target.options, function(o) { return o.selected }) .map(function(o) { var val = "_value" in o ? o._value : o.value; return + ("val") }) ${value} = "$event.target.multiple ? $$selectedVal : $$selectedVal[0]" ` addHandler(el, "change", code, null, true); }
綁定值
select 元素綁定的屬性是 selectedIndex,但是 select 并沒有在 genSelect 方法中調用addProp 綁定某個屬性
那么 select 在哪里設置了呢?Vue 專門使用了方法 setSelected 設置 selectedIndex,這個方法現在不說,你只要知道,他是更新 selectedIndex 的就好了,后面會有一篇專門說
疑惑為什么 select 不像 input 一樣直接綁定 value,這樣,不是也可以確定選項嗎?
按我的理解呢,我覺得應該是原始select的 value 只有字符串一類型的值,而 Vue 的select 支持 數字和字符串兩種類型的值啊
拼接事件
觀察下面的渲染函數,就可以很清楚地名表,select 的回調是怎么一回事了
1、從所有option 中 篩選出被選擇的option
2、使用數組保存所有篩選后的option的value
3、判斷是否多選,多選返回數組,單選返回數組第一項
然后,你還能知道 select 綁定的是 change 事件
獻上 select 的渲染 render 函數
with(this) { return _c("select", { directives: [{ name: "model", rawName: "v-model", value: (name), expression: "name" }], on: { "change": function($event) { var $$selectedVal = Array.prototype.filter .call($event.target.options,function(o) { return o.selected }) .map(function(o) { var val = "_value" in o ? o._value: o.value; return val }) name = $event.target.multiple ? $$selectedVal: $$selectedVal[0]; } } }) }
雙向更新
render 執行時,directive 處從實例讀取了 name, name 收集到 本組件 watcher
1、內部變化,通知更新 watcher,上面 render 重新執行,獲取新name,于是更新 select 元素屬性 selectedIndex,于是select 當前選項就改變了
2、外部變化,直接賦值給 綁定值,綁定值變化,通知 watcher 更新,更新完,重新設置 selectedIndex
CheckboxgenCheckboxModel 源碼奉上
function genCheckboxModel(el, value, modifiers) { var valueBinding = el.value || "null"; var trueValueBinding = el["true-value"] || "true"; var falseValueBinding = el["false-value"] || "false"; addProp(el, "checked", `Array.isArray(${value})? _i(${value},${valueBinding})>-1 ${trueValueBinding === "true"? ":(" + value + ")" : ":_q(" + value + "," + trueValueBinding + ")"}` ); addHandler(el, "change", `var $$a= ${value}, $$el=$event.target, $$c = $$el.checked?(${trueValueBinding}):(${falseValueBinding}); if(Array.isArray($$a)){ var $$v= (${number? "_n(" + valueBinding+")":valueBinding}), $$i = _i($$a,$$v); if($$el.checked){ $$i<0&&(${value}=$$a.concat([$$v])) }else{ $$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1))) } }else{ ${value} = $$c }`,null, true ); }
綁定值
賦值給 checked
看上面的方法就知道啦,調用 addProps,設置 checked 值
拼接事件
哈哈,還是看下面的渲染函數,看下 checkbox 的回調,其實意思就是
1、數組,分是否選擇
a. 選擇,把當前選項 concat 進數組
b. 取消選擇,把當前選項 移除出數組
2、非數組,直接賦值
你還能知道 checkbox 綁定的是 change 事件
來看看checkbox 的渲染render函數
with(this) { return _c("input", { directives: [{ name: "model", rawName: "v-model", value: (name), expression: "name" }], attrs: { "type": "checkbox", "value": "1" }, domProps: { // _i方法,作用是,判斷第二個參數是否在 第一個參數數組中 "checked": Array.isArray(name) ? _i(name, "1") > -1 : (name) }, on: { "change": function($event) { var $$a = name, $$el = $event.target, $$c = $$el.checked ? (true) : (false); if (Array.isArray($$a)) { var $$v = "1", $$i = _i($$a, $$v); if ($$el.checked) { $$i < 0 && (name = $$a.concat([$$v])) } else { $$i > -1 && (name = $$a.slice(0, $$i).concat($$a.slice($$i + 1))) } } else { name = $$c }; } } }) }Radio
處理 radio 元素的 genRadioModel 源碼
function genRadioModel(el, value) { var valueBinding = el.value|| "null"; addProp(el, "checked", ("_q(" + value + "," + valueBinding + ")")); addHandler(el, "change", `${value} = ${valueBinding}`, null, true); }
怎么賦值
直接賦值給 checked,你看上面的方法調用 addProp 可以看到
拼接事件
這個真的更加簡單了...比 input 還簡單啊,都不用獲取值,只是直接賦值為 radio 的值
name="1", 1 是你設置給 radio 的value值
你還能知道 radio 綁定的是 change 事件
看下下面的radio 的渲染函數你就懂了
with(this) { return _c("input", { directives: [{ name: "model", rawName: "v-model", value: (name), expression: "name" }], attrs: { "type": "radio", "value": "1" }, domProps: { // _q 方法,作用是,判斷兩個參數是否相等 "checked": _q(name, "1") }, on: { "change": function($event) { name = "1"; } } }) }
雙向更新
在 render 執行的時候,綁定值 收集到 本組件的 watcher
1、內部變化,通知更新 watcher,render 重新執行,獲取新的 name,更新 radio 元素屬性 checked
2、外部變化,直接賦值 更新 綁定值 name 等于 radio元素屬性 value
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105203.html
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之詳解今天我們來看看處 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 ...
摘要:執行的時候,會綁定上下文對象為組件實例于是中的就能取到組件實例本身,的代碼塊頂層作用域就綁定為了組件實例于是內部變量的訪問,就會首先訪問到組件實例上。其中的獲取,就會先從組件實例上獲取,相當于。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得...
摘要:因為失去焦點之后被強制更新了一波嗯,這就是的作用,把頁面上的顯示值也過濾一遍 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】VModel - 源碼版之input詳...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之屬性解析哈哈哈,今天終 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之節點數據拼接上一篇我們 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
閱讀 982·2023-04-26 01:47
閱讀 1672·2021-11-18 13:19
閱讀 2042·2019-08-30 15:44
閱讀 645·2019-08-30 15:44
閱讀 2291·2019-08-30 15:44
閱讀 1232·2019-08-30 14:06
閱讀 1420·2019-08-30 12:59
閱讀 1900·2019-08-29 12:49