你真的瞭解 v-model 嗎?

衆所周知,v-model 是 Vue.js 中實現的一個語法糖,和 Vue.js 中推崇的單向數據流表現不一致,用於實現所謂的雙向綁定。html

但看似簡單的 v-model 具體是怎麼作到雙向綁定的,爲了知足下好奇心,不得不深刻到源碼中看一看。vue

v-model 的使用情景分爲兩種:直接用到 inputtextarea 等輸入控件中;用於自定義組件中。之因此分爲這兩類是由於它們在 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 函數中調用的函數正是這個屬性的值 updateDOMPropsupdateDOMProps 負責處理 innerTextinnerHTML, 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 中會包含 domPropson 兩個屬性,而後將 domProps 中的 value 值賦給 DOM 作初始值,併爲 DOM 的 input 事件綁定 on 中的 input 函數;在組件中,編譯器會編譯出 model 屬性,model 屬性包含了 value 值和 callback。而組件實例化時,會根據組件預約義的 model 配置將 model.value 傳入組件的 prop ,並將 callback 做爲組件自定義事件中 input 事件的回調 。

相關文章
相關標籤/搜索