Vue v-model 指令详解

Vue 提供了v-model 指令来帮助用户方便的处理表单元素的数据绑定和用户输入内容同步到数据上(双向绑定)。
用法如下:

<input v-model="test" type="text"/>

初始test的值会绑定到input的value中
当用户修改input的内容
test的变量也同时更新。

"_c('input',{directives:[{name:"model",rawName:"v-model",value:(test),expression:"test"}]
,attrs:{"type":"text"},domProps:{"value":(test)},
on:{"input":function($event){if($event.target.composing)return;
test=$event.target.value}}})"

上面是vue 为v-model生成的render 代码,我们可以看到v-model 最终除了绑定默认的值外,还添加了一个input事件,input事件将input的值再重新传给绑定的值。所以v-model其实是一个双向绑定的语法糖
转换方法

function genDefaultModel (
    el,
    value,
    modifiers
  ) {
    var type = el.attrsMap.type;

    // warn if v-bind:value conflicts with v-model
    // except for inputs with v-bind:type
    {
      var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value'];
      var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'];
      if (value$1 && !typeBinding) {
        var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value';
        warn$1(
          binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " +
          'because the latter already expands to a value binding internally',
          el.rawAttrsMap[binding]
        );
      }
    }

    var ref = modifiers || {};
    var lazy = ref.lazy;
    var number = ref.number;
    var trim = ref.trim;
    var needCompositionGuard = !lazy && type !== 'range';
    // lazy 修饰符会强制将event改为change
    var event = lazy
      ? 'change'
      : type === 'range'
        ? RANGE_TOKEN
        : 'input';

    var valueExpression = '$event.target.value';
    if (trim) {
    // trim修饰符会处理值的前后空格
      valueExpression = "$event.target.value.trim()";
    }
    if (number) {
     // number修饰符会尝试将数字进行number转换,失败了还保留原值
      valueExpression = "_n(" + valueExpression + ")";
    }

    var code = genAssignmentCode(value, valueExpression);
    if (needCompositionGuard) {
    //  生成input事件代码
      code = "if($event.target.composing)return;" + code;
    }

    addProp(el, 'value', ("(" + value + ")"));
    addHandler(el, event, code, null, true);
    if (trim || number) {
      addHandler(el, 'blur', '$forceUpdate()');
    }
  }

  function genAssignmentCode (
    value,
    assignment
  ) {
    var res = parseModel(value);
    if (res.key === null) {
      return (value + "=" + assignment)
    } else {
      return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
    }
  }

如果我们给一个自定义组件添加v-model
因为v-model默认是需要绑定在具有input或者change事件的表单元素上的。所以对于非表单元素比如封装了表单元素的自定义组件,v-model是需要我们自己来实现这个语法糖的事件传递的,具体做法参考下面代码:

Vue.component('base-input', {
  props: {
    ‘value’: String
  },
  template: `
    <input
      type="text"
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})
<base-input v-model="test"></base-input>

组件v-model生成的中间代码

"_c('div',{attrs:{"id":"app"}},
[_c('test',{model:{value:(test),
callback:function ($$v) {
    test=$$v
},expression:"test"}})],1)"

在这里插入图片描述
最终model里的callback会转化为组件vnode里的 on事件map里了。
转换方法:

function transformModel (options, data) {
    var prop = (options.model && options.model.prop) || 'value';
    var event = (options.model && options.model.event) || 'input'
    ;(data.attrs || (data.attrs = {}))[prop] = data.model.value;
    var on = data.on || (data.on = {});
    var existing = on[event];
    var callback = data.model.callback;
    if (isDef(existing)) {
      if (
        Array.isArray(existing)
          ? existing.indexOf(callback) === -1
          : existing !== callback
      ) {
        on[event] = [callback].concat(existing);
      }
    } else {
     // callback 传入到on的map里了
      on[event] = callback;
    }
  }

所以在组件内部我们要将form元素的 input事件(或者radio、checkbox的change事件)通过$emit的方法传出来。外面的组件上vue已经帮我们默认添加好了input事件来接受参数。

v-model 完整的流程代码:
1.处理v-model指令生成代码

function genDirectives (el, state) {
    var dirs = el.directives;
    if (!dirs) { return }
    var res = 'directives:[';
    var hasRuntime = false;
    var i, l, dir, needRuntime;
    for (i = 0, l = dirs.length; i < l; i++) {
      dir = dirs[i];
      needRuntime = true;
      // 这里取model的gen方法 处理v-model此时 dir.name = "model"
      var gen = state.directives[dir.name];
      if (gen) {
        // compile-time directive that manipulates AST.
        // returns true if it also needs a runtime counterpart.
        needRuntime = !!gen(el, dir, state.warn);
      }
      if (needRuntime) {
        hasRuntime = true;
        res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:" + (dir.isDynamicArg ? dir.arg : ("\"" + (dir.arg) + "\""))) : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
      }
    }
    if (hasRuntime) {
      return res.slice(0, -1) + ']'
    }
  }

不同的form表单元素绑定会有一些处理细节上的差别)

function model (
    el,
    dir,
    _warn
  ) {
    debugger
    warn$1 = _warn;
    var value = dir.value;
    var modifiers = dir.modifiers;
    var tag = el.tag;
    var type = el.attrsMap.type;

    {
      // inputs with type="file" are read only and setting the input's
      // value will throw an error.
      if (tag === 'input' && type === 'file') {
        warn$1(
          "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" +
          "File inputs are read only. Use a v-on:change listener instead.",
          el.rawAttrsMap['v-model']
        );
      }
    }
	 // 组件上的v-model的处理
    if (el.component) {
      genComponentModel(el, value, modifiers);
      // component v-model doesn't need extra runtime
      return false
    } else if (tag === 'select') { // select 标签的处理
      genSelect(el, value, modifiers);
    } else if (tag === 'input' && type === 'checkbox') { // input type为checkbox的处理
      genCheckboxModel(el, value, modifiers);
    } else if (tag === 'input' && type === 'radio') {// input type为radio的处理
      genRadioModel(el, value, modifiers);
    } else if (tag === 'input' || tag === 'textarea') {// input type为textarea的处理
      genDefaultModel(el, value, modifiers);
    } else if (!config.isReservedTag(tag)) {// 非常规标签,按照自定义组件处理
      genComponentModel(el, value, modifiers);
      // component v-model doesn't need extra runtime
      return false
    } else {
      warn$1(
        "<" + (el.tag) + " v-model=\"" + value + "\">: " +
        "v-model is not supported on this element type. " +
        'If you are working with contenteditable, it\'s recommended to ' +
        'wrap a library dedicated for that purpose inside a custom component.',
        el.rawAttrsMap['v-model']
      );
    }

    // ensure runtime directive metadata
    return true
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森哥的歌

一杯咖啡半包烟,助我熬过那长夜

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值