衆所周知,v-model
是 Vue.js 中實現的一個語法糖,和 Vue.js 中推崇的單向數據流表現不一致,用於實現所謂的雙向綁定。html
但看似簡單的 v-model
具體是怎麼作到雙向綁定的,爲了知足下好奇心,不得不深刻到源碼中看一看。vue
v-model
的使用情景分爲兩種:直接用到 input
或 textarea
等輸入控件中;用於自定義組件中。之因此分爲這兩類是由於它們在 Vue 源碼中的實現的有差別的。node
v-model
<div id="app">
<input type="text" v-model="aa" >
</div>
<script>
export default {
data() {
return {
aa: 'hello'
}
}
}
</script>
複製代碼
寫一個如上的單頁面組件,首先會發生什麼?web
若是咱們用的是不帶編譯器版本的 Vue , 那麼 vue-loader
會將其這個文件編譯爲 一個 render
函數,結果以下:express
(function anonymous( ) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('input',{directives:[{name:"model",rawName:"v-model",value:(aa),expression:"aa"}],attrs:{"type":"text"},domProps:{"value":(aa)},on:{"input":function($event){if($event.target.composing)return;aa=$event.target.value}}})])}
})
複製代碼
着重看 input
被編譯出來的結果:瀏覽器
[_c('input',
{directives:[{name:"model",rawName:"v-model",value:(aa),expression:"aa"}],
attrs:{"type":"text"},domProps:{"value":(aa)},
on:{"input":function($event){if($event.target.composing)return;aa=$event.target.value}}})]
複製代碼
從上面就能夠大概看出所謂語法糖的由來了,首先在爲 input
設置了名爲 value
的屬性,再在 on
中寫了一個 input
事件。看上去也很簡單,但這些屬性和事件又是怎麼和真實 DOM 掛上鉤的?從下面幾個點分析:app
組件掛載的具體邏輯在源碼中的 「src\core\vdom\patch.js」,而後咱們重點看其中的 createEl
函數,它負責將 VNode 轉爲真實 DOM,節點賦值和事件監聽都發生在這個過程當中。其中的關鍵就是它調用了 invokeCreateHooks
函數,這個函數負責調用組件 create
以前的全部鉤子函數(不是生命週期鉤子函數),具體邏輯以下:dom
// 執行平臺相關的 DOM 屬性操做以及事件監聽操做
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
複製代碼
可能你又要問這個 cbs.create
是哪來的?因爲不一樣平臺下 DOM 的有關操做差別很大,爲了實現跨平臺, Vue 將這些操做都單獨封裝起來。若是要看 web 平臺相關的詳細邏輯能夠去 「src\platforms\web\runtime\modules\index.js」 中查找。今天咱們只看 domProps
和事件監聽的處理:函數
// src\platforms\web\runtime\modules\dom-props.js
export default {
create: updateDOMProps,
update: updateDOMProps
}
複製代碼
能夠看到這個文件輸出了一個對象,這個對象又有一個 create
屬性, invokeCreateHooks
函數中調用的函數正是這個屬性的值 updateDOMProps
。 updateDOMProps
負責處理 innerText
, innerHTML
, value
等邏輯。ui
// src\platforms\web\runtime\modules\events.js
export default {
create: updateDOMListeners,
update: updateDOMListeners
}
複製代碼
一樣的,事件監聽的邏輯也相似。updateDOMListeners
根據 VNode.data.on
中的邏輯處理事件監聽器的更新,掛載和刪除。除此以外,directives
中編譯出的 v-model
實現方式與前面一致,Vue 中有對 v-model
這個執行作特殊處理,解決了一些瀏覽器兼容性上的問題,以及不一樣輸入控件的兼容問題,有興趣的小夥伴能夠看看源碼中 「src\platforms\web\runtime\directives\model.js」 的處理。
v-model
<div id="app">
<compo v-model="aa" />
</div>
<script>
export default {
data() {
return {
aa: 'hello'
}
}
}
</script>
// compo
const compo = Vue.component('compo', {
template: '<input :value="$attrs.value" @input="handleInput" />',
methods: {
handleInput(e) {
this.$emit('input', e.target.value)
}
},
})
複製代碼
從 compo
中就能清楚的看到用於兩種情景下 v-model
的差別,須要在組件中的input
元素上手動去綁定一個 @input
事件,並讓其給父組件傳遞一個 input
事件。先看看這種狀況下編譯出來的結果:
(function anonymous( ) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('compo',{model:{value:(aa),callback:function ($$v) {aa=$$v},expression:"aa"}})],1)}
})
// compo 編譯結果
[_c('compo',{model:{value:(aa),callback:function ($$v) {aa=$$v},expression:"aa"}})]
複製代碼
你們發現問題沒有,組件中的 v-model
並無編譯出 input
事件,只是一個簡單的 callback
。那要如何在源碼中找到響應處理呢?
組件由 render
函數變爲 VNode
的主要邏輯在 「src\core\vdom\create-component.js」 中的 createComponent
函數,之後出現相似問題均可以從這個函數入手。這個函數中對組件的 model
屬性有以下的特殊處理:
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = callback
}
}
複製代碼
如今回頭看 callback
是怎麼和 input
事件掛鉤是否是一目瞭然了,同時裏面還有組件中 model
屬性的配置,好比,不想要綁定 input
事件,要處理 change
事件,也是在這段源碼中實現的。
因此,咱們回頭來總結下:
v-model
是什麼?
v-model
是 Vue 中內置的一個指令
爲何使用 v-model
?
它方便呀~~一個 v-model
搞定 value
和 @input
,一句話解決的問題,毫不用兩句
怎麼使用 v-model
?
<input v-model="cc />"
// 至關於
<input :value="cc" @input="handleInput"
<compo v-model="cc" />
複製代碼
v-model
是怎樣實現的?
v-model
在組件中和在 DOM 元素中的實現是不一樣的:在 DOM 元素中,編譯器會根據真實 DOM 建立虛擬 DOM ,而虛擬 DOM 中會包含 domProps
和 on
兩個屬性,而後將 domProps
中的 value
值賦給 DOM 作初始值,併爲 DOM 的 input
事件綁定 on
中的 input
函數;在組件中,編譯器會編譯出 model
屬性,model
屬性包含了 value
值和 callback
。而組件實例化時,會根據組件預約義的 model
配置將 model.value
傳入組件的 prop
,並將 callback
做爲組件自定義事件中 input
事件的回調 。