v-model 是怎麼實現的?

tangchaoren發表於2018-12-12

表單v-model

在vue中,v-model無疑是最常用的API之一了,像 input、textarea、radio、checkbox、select等都可以使用v-model實現雙向繫結。那麼它具體是怎麼實現的呢? 下面通過自己寫的一個demo來具體分析一下?

v-model 是怎麼實現的?

directives

在vue的parse階段也就是將AST樹生成code的過程中會去收集元素上定義的指令

v-model 是怎麼實現的?
這個時候我們可以看到dir.name是model(這個model指的是指令名稱model), 然後去拿到gen,實際上就是model繫結的函式,那麼這個時候會執行到genDefaultModel(el, value, modifiers);其中el是指tag的AST物件, value是model, modifiers是undefined。
v-model 是怎麼實現的?
那麼我們來看下genDefaultModel這個函式的實現
v-model 是怎麼實現的?
這個函式中首先執行了modifiers,它的不同主要影響event和valueExpression ,那麼在我們的這個demo中很顯然,event是input。valueExpression 是$event.target.value,然後接下來去執行genAssignmentCode生成code,我們來看下genAssignmentCode的實現
v-model 是怎麼實現的?
這裡執行了parseModel,那麼在我們的這個demo裡,value就是model所以res.key==null那麼就得到

model = $event.target.value
複製程式碼

再回到genDefaultModel,接下來會執行兩個非常重要的方法

  addProp(el, 'value', ("(" + value + ")"));
  addHandler(el, event, code, null, true);
複製程式碼

第一個方法addProp實際上就是給我們的input標籤新增一個prop,相當於動態繫結了value, 第二個方法addHandler就是給input標籤新增一個input事件,並且在事件觸發的時候動態修改model的值。如此,也就實現了雙向繫結。

<input
  v-bind:value="model"
  v-on:input="model=$event.target.model">
複製程式碼

最後再回到genDirectives,會根據指令生成一個render函式

元件v-model

let baseSpan = {
  template: '<div><span v-show="isShow">我是顯示還是不顯示呢</span>' + '<button  @click="changeValue">點選</button></div>',
  props: ['value'],
  name: 'baseSpan',
  data(){
    return {
      isShow: this.value
    }
  },
  methods: {
    changeValue() {
      this.isShow = true
      this.$emit('input', true)
    }
  }
}
 var app = new Vue({
  el: '#app',
  data: {
    visible: false
  },
  components: {
    baseSpan
  }
})
Vue.component('base-span',baseSpan);
複製程式碼

html程式碼如下

<div id='app'>
    <base-span v-model='visible'></base-span>
      <span>{{visible}}</span>
 </div>
複製程式碼

父元件的visible資料關聯到了子元件baseSpan v-model指令上,而子元件上定義了一個value的props,以及一個changeValue方法,方法內部通過派發一個input事件向父元件傳遞資料。 這是典型的父子元件通訊方式,也是v-model生效的必要條件。當然這個value和input並不是寫死的,我們可以根據實際需要在子元件的model配置中進行自定義,比如,我們派發的事件也可以是一個change。那麼我們來看下原始碼是如何設計的?


  if (el.component) {
    genComponentModel(el, value, modifiers);
    // component v-model doesn't need extra runtime
    return false
}
複製程式碼

元件的v-model同樣執行指令的model函式,然後命中上面的if語句,進而執行genComponentModel(el, value, modifiers)方法。

function genComponentModel (
  el,
  value,
  modifiers
) {
  var ref = modifiers || {};
  var number = ref.number;
  var trim = ref.trim;

  var baseValueExpression = '$$v';
  var valueExpression = baseValueExpression;
  if (trim) {
    valueExpression =
      "(typeof " + baseValueExpression + " === 'string'" +
      "? " + baseValueExpression + ".trim()" +
      ": " + baseValueExpression + ")";
  }
  if (number) {
    valueExpression = "_n(" + valueExpression + ")";
  }
  var assignment = genAssignmentCode(value, valueExpression);

  el.model = {
    value: ("(" + value + ")"),
    expression: ("\"" + value + "\""),
    callback: ("function (" + baseValueExpression + ") {" + assignment + "}")
  };
}
複製程式碼

針對我們這個例子而言會生成如下的el.model

v-model 是怎麼實現的?
當genDirectives執行完畢後,會執行

 // component v-model
  if (el.model) {
    data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
  }
複製程式碼

這樣父元件的render函式會包含子元件model的配置項,那麼在建立子元件vnode階段,會執行createComponent函式。 然後執行如下的邏輯

 // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data);
  }

複製程式碼

那麼transformModel主要是將model繫結的資料visible新增到data.props中。然後在on中新增data.model.callback.

// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data) {
  var prop = (options.model && options.model.prop) || 'value';
  var event = (options.model && options.model.event) || 'input';(data.props || (data.props = {}))[prop] = data.model.value;
  var on = data.on || (data.on = {});
  if (isDef(on[event])) {
    on[event] = [data.model.callback].concat(on[event]);
  } else {
    on[event] = data.model.callback;
  }
}
複製程式碼

其實就相當於父元件將visible通過props傳遞給子元件的value。然後子元件改變資料的時候,通過派發事件通知父元件更新visible。通過demo演示就是:當我點選按鈕的時候,isShow=true 子元件中的span顯示。visible = true。

v-model 是怎麼實現的?
再次點選按鈕 isShow = false。 visible = false

v-model 是怎麼實現的?

可見無論是表單元素的v-model還是元件的v-model。他們的本質就是一種語法糖。

心得體會

公司目前正在使用vue,閒暇之餘自己寫demo,通過單步除錯的方法,一步一步的去理解vue原始碼。如有不正之處,還請不吝賜教。

相關文章