v-model 是怎么实现的?

本文详细剖析Vue中v-model的实现原理,包括其在表单元素和组件间的双向数据绑定机制,通过代码示例解释如何通过v-model实现数据同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

表单v-model

在vue中,v-model无疑是最常用的API之一了,像 input、textarea、radio、checkbox、select等都可以使用v-model实现双向绑定。那么它具体是怎么实现的呢? 下面通过自己写的一个demo来具体分析一下?

directives

在vue的parse阶段也就是将AST树生成code的过程中会去收集元素上定义的指令

这个时候我们可以看到dir.name是model(这个model指的是指令名称model), 然后去拿到gen,实际上就是model绑定的函数,那么这个时候会执行到genDefaultModel(el, value, modifiers);其中el是指tag的AST对象, value是model, modifiers是undefined。
那么我们来看下genDefaultModel这个函数的实现
这个函数中首先执行了modifiers,它的不同主要影响event和valueExpression ,那么在我们的这个demo中很显然,event是input。valueExpression 是$event.target.value,然后接下来去执行genAssignmentCode生成code,我们来看下genAssignmentCode的实现
这里执行了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

当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。

再次点击按钮 isShow = false。 visible = false

可见无论是表单元素的v-model还是组件的v-model。他们的本质就是一种语法糖。

心得体会

公司目前正在使用vue,闲暇之余自己写demo,通过单步调试的方法,一步一步的去理解vue源码。如有不正之处,还请不吝赐教。

Vue.js 中的 `v-model` 指令本质上是一种语法糖,用于简化表单元素与数据之间的双向绑定操作。其核心原理是结合了数据绑定和事件监听机制,从而实现数据与视图的同步更新。 ### 基本实现机制 1. **属性绑定与事件监听** 在 `v-model` 的实现中,不同的表单元素使用不同的属性和事件来实现双向绑定: - 对于 `<input>` 和 `<textarea>` 元素,`v-model` 使用 `value` 属性和 `input` 事件。当用户输入内容时,触发 `input` 事件,更新绑定的数据。 - 对于复选框(checkbox)和单选按钮(radio),`v-model` 使用 `checked` 属性和 `change` 事件。当用户选择或取消选择时,通过 `change` 事件更新数据。 - 对于 `<select>` 元素,`v-model` 使用 `value` 属性和 `change` 事件来更新数据[^2]。 2. **忽略初始值** `v-model` 会忽略所有表单元素的 `value`、`checked`、`selected` 特性的初始值,而总是以 Vue 实例中的数据作为数据来源。因此,建议在组件的 `data` 选项中声明初始值。 3. **语法糖的实现** `v-model` 可以被看作是 `:value` 与 `@input` 的结合体。例如,以下代码展示了 `v-model` 的等价写法: ```html <!-- 使用 v-model --> <input type="text" v-model="text"> <!-- 等价于 --> <input type="text" :value="text" @input="text = $event.target.value"> ``` 这种方式简化了开发者对数据绑定和事件监听的处理,使得代码更加简洁和易读[^4]。 ### 数据响应式与双向绑定 Vue.js 的响应式系统基于 `Object.defineProperty`(Vue 2.x)或 `Proxy`(Vue 3.x)实现。通过数据劫持和发布订阅模式,Vue 能够监听数据的变化,并在数据变化时自动更新视图。`v-model` 利用这一机制,在用户输入时更新数据,并在数据变化时更新视图。 例如,在一个简单的 Vue 组件中,`v-model` 会自动将 `value` 作为组件的属性,并将 `input` 作为事件名,从而实现数据的双向绑定: ```html <template> <div> <input type="text" v-model="inputValue"> <p>输入的内容: {{ inputValue }}</p> </div> </template> <script> export default { data() { return { inputValue: '' }; } }; </script> ``` 在这个例子中,`inputValue` 是数据模型的一部分,`v-model` 会监听 `input` 事件并更新 `inputValue`,同时将 `inputValue` 的值作为 `value` 属性绑定到 `<input>` 元素上。 ### 特殊场景的处理 尽管 `v-model` 在大多数情况下都能很好地工作,但在某些复杂场景下可能会遇到限制。例如,在创建复选框或单选按钮时,`v-model` 的行为可能不如预期。此时,开发者需要手动处理数据绑定和事件更新,以确保数据的正确性[^4]。 --- ### 总结 `v-model` 是 Vue.js 中用于实现双向数据绑定的核心指令之一。它通过结合属性绑定和事件监听机制,简化了开发者对表单元素与数据之间同步的处理。尽管 `v-model` 是一种语法糖,但其背后依赖于 Vue 的响应式系统,确保数据与视图能够高效地同步更新。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值