vue动态绑定v-model属性名方式

目录
  • vue动态绑定v-model属性名
    • 1.目标
    • 2.方案
  • vue双向绑定原理(v-model)
    • 表单绑定
    • 组件使用v-model

vue动态绑定v-model属性名

1.目标

首先配置列,根据配置渲染表单,每个表单项绑定配置中的属性

2.方案

<template v-for="(item) in showQueryColumns" >
    <el-col :key="item.prop" :xs = "24" :sm = "12" :md="12" :lg = "12" :xl = "6">
        <!--字符串类型-->
        <el-form-item v-if="item.type==='string'" :label="$t(item.i18n)" :prop="item.prop">
             <el-input v-model="form[item.prop]" clearable></el-input>
        </el-form-item>
    </el-col>
 </template>
  • v-model绑定的必须是属性,可以使用方括号,取指定对象的属性

亲测有效

vue双向绑定原理(v-model)

之前有整理过Vue响应式原理,响应式主要的效果是数据改变了就会引起页面修改。关于v-model我们也不陌生,vue的双向绑定指令,页面修改会引起数据修改,数据修改页面也会跟着改变。我们直到数据->页面是由vue的响应式原理实现的,那么该怎么做到页面->数据的修改呢?

表单绑定

v-model一般我们是在表单元素上进行使用,因为视图能影响数据,本质上是这个视图需要可交互,因此表单是实现这一交互的前提。表单的使用是以<input>、<textarea>、<select>为核心。具体的使用细节这里就不一一细说了。

这里我们从模板解析开始分析,vue对v-model做了什么操作。

这里我们来看一些绑定在input上的v-model都经历了什么。

// 普通输入框
<input type="text" v-model="value1">

AST树的解析

模版的编译阶段,会调用var ast = parse(template.trim(), options)生成AST树,parse函数的起他细节这里不展开分析,我们只说模板属性上的解析processAttrs函数。

vue模板属性有两部分组成,一部分是指令,另一部分是普通的html标签属性。对于指令,出去v-on和v-bind,其他普通指令会执行addDirective过程。

// 处理模板属性
function processAttrs(el) {
  var list = el.attrsList;
  var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name; // v-on:click
    value = list[i].value; // doThis
    if (dirRE.test(name)) { // 1.针对指令的属性处理
      ···
      if (bindRE.test(name)) { // v-bind分支
        ···
      } else if(onRE.test(name)) { // v-on分支
        ···
      } else { // 除了v-bind,v-on之外的普通指令
        ···
        // 普通指令会在AST树上添加directives属性
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i]);
        if (name === 'model') {
          checkForAliasModel(el, value);
        }
      }
    } else {
      // 2. 普通html标签属性
    }
  }
}

在AST产生阶段对事件指令v-on的处理是为AST树添加events属性。类似的,普通指令会在AST树上添加directives属性,我们可以看一下addDirective函数

// 添加directives属性
function addDirective (el,name,rawName,value,arg,isDynamicArg,modifiers,range) {
    (el.directives || (el.directives = [])).push(rangeSetItem({
      name: name,
      rawName: rawName,
      value: value,
      arg: arg,
      isDynamicArg: isDynamicArg,
      modifiers: modifiers    // 模板中添加的修饰符,如:.lazy、.number、.trim
    }, range));
    el.plain = false;
  }

最终AST树上会多处一个属性对象

// AST
{
  directives: {
    {
      rawName: 'v-model',
      value: 'value',
      name: 'v-model',
      modifiers: undefined
    }
  }
}

render函数生成

render函数生成阶段,generate逻辑,其中genData会对模版的各个属性进行处理,最终返回拼接好的字符串模板,而对指令的处理会进入genDirectives函数

在genDirectives函数中,会拿到之前AST树中的directives对象,并遍历解析指令对象,最终以'directives:['包裹的字符串返回。

// directives render字符串的生成
  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;
      // 对指令ast树的重新处理
      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) + ']'
    }
  }

这里有一句关键代码var gen = state.directives[dir.name],这里的的dir.name为model,这个model回去执行对应的model函数。我们来看一下model函数的逻辑。

unction model (el,dir,_warn) {
    warn$1 = _warn;
    // 绑定的值
    var value = dir.value;
    var modifiers = dir.modifiers;
    var tag = el.tag;
    var type = el.attrsMap.type;
    {
      // 这里遇到type是file的html,如果还使用双向绑定会报出警告。
      // 因为File inputs是只读的
      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') {
      // checkbox表单
      genCheckboxModel(el, value, modifiers);
    } else if (tag === 'input' && type === 'radio') {
      // radio表单
      genRadioModel(el, value, modifiers);
    } else if (tag === 'input' || tag === 'textarea') {
      // 普通input,如 text, 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 {
      // 如果不是表单使用v-model,同样会报出警告,双向绑定只针对表单控件。
      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
  }

我们可以看到对于v-model的处理,在这一步上会根据使用场景处理调用不同的处理。单是对每种类型对应的事件处理响应机制也不同。因此我们需要针对不同的表单控件生成不同的render函数,所以需要产生不同的AST属性。model针对不同类型的表单控件有不同的处理分支。我们来看普通input标签的处理,genDefalutModel分支。

function genDefaultModel (el,value,modifiers) {
    var type = el.attrsMap.type;
    // v-model和v-bind值相同值,有冲突会报错
    {
      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]
        );
      }
    }
    // modifiers存贮的是v-model的修饰符。
    var ref = modifiers || {};
    // lazy,trim,number是可供v-model使用的修饰符
    var lazy = ref.lazy;
    var number = ref.number;
    var trim = ref.trim;
    var needCompositionGuard = !lazy && type !== 'range';
    // lazy修饰符将触发同步的事件从input改为change
    var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input';
    var valueExpression = '$event.target.value';
    // 过滤用户输入的首尾空白符
    if (trim) {
      valueExpression = "$event.target.value.trim()";
    }
    // 将用户输入转为数值类型
    if (number) {
      valueExpression = "_n(" + valueExpression + ")";
    }
    // genAssignmentCode函数是为了处理v-model的格式,允许使用以下的形式: v-model="a.b" v-model="a[b]"
    var code = genAssignmentCode(value, valueExpression);
    if (needCompositionGuard) {
      //  保证了不会在输入法组合文字过程中得到更新
      code = "if($event.target.composing)return;" + code;
    }
    //  添加value属性
    addProp(el, 'value', ("(" + value + ")"));
    // 绑定事件
    addHandler(el, event, code, null, true);
    if (trim || number) {
      addHandler(el, 'blur', '$forceUpdate()');
    }
  }
function genAssignmentCode (value,assignment) {
  // 处理v-model的格式,v-model="a.b" v-model="a[b]"
  var res = parseModel(value);
  if (res.key === null) {
    // 普通情形
    return (value + "=" + assignment)
  } else {
    // 对象形式
    return ("$set(" + (res.exp) + ", " + (res.key) + ", " + assignment + ")")
  }
}

该函数主要逻辑是两个部分,一部分是针对修饰符产生不同的事件处理字符串,二是为v-model产生的AST树,添加属性和事件相关的属性。其中最核心的两行代码是:

//  添加value属性
addProp(el, 'value', ("(" + value + ")"));
// 绑定事件属性
addHandler(el, event, code, null, true);

addHandler函数会为AST树添加事件相关的属性,addProp会为AST树添加props属性。最终AST树新增了两个属性。

到这里我们会发现,通过genDirective处理后,原先的AST树新增了两个属性。所以在字符串生成阶段同样需要处理props和event的分支

function genData$2 (el, state) {
  var data = '{';
  // 已经分析过的genDirectives
  var dirs = genDirectives(el, state);
  // 处理props
  if (el.props) {
    data += "domProps:" + (genProps(el.props)) + ",";
  }
  // 处理事件
  if (el.events) {
    data += (genHandlers(el.events, false)) + ",";
  }
}

最终render函数的结果为:

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

总结

如果到这里比较迷糊的话,我们来整理一下整体的流程

  • 在生成AST阶段,处理到属性时进入processAttrs,在该函数中判断该属性是否为指令,是指令判断是不是v-on、v-bind,如果都不是就进入addDirective
  • 通过addDirective函数为AST树上添加了directives中的一个对象
  • 然后根据AST树,生成render函数过程中,需要在genData中调用genDirectives,进入指令处理流程。
  • genDirectives拿到指令对象后,会遍历指令对象,使用state.directives[dir.name]对指令对象进行解析和处理。这里的dir.name是model。会调用一个model函数,在该函数中根据v-model的应用标签类型,处理成不同的AST属性。input输入框类型,会调用genDefaultModel函数,在其中做类型判断,修饰符处理,然后通过addProp为AST添加props属性,addHandler会为AST语法树的events属性中添加对应事件监听。
  • 最后根据AST语法树生成render函数时,绑定的属性会以props的形式存在domProps中,另一个是以事件的形式存储input事件,并保留在on属性中

patch真实节点

当我们的render函数生成以后,执行render函数,生成对应的vnode。

有了新的vnode以后需要执行patchVnode。前面得到的指令相关的信息会保留在vnode的data属性里,所以对属性的处理也会走针对指令处理的函数incokeCreateHooks。

在该函数中对指令的处理包括:

  • 判断vnode data上存在domProps属性,调用 updateDOMProps更新input标签的value值
  • 调用updateAttrs函数,根据attrs属性更新节点的属性值
  • 判断vnode data上存在on属性,调用updateDomListeners为dom添加事件监听

总结:所以v-model语法糖最终是通过监听表单控件自身的某些事件(不同类型的标签会有不同的监听事件类型,后面会列出来),去影响自身的value值。等同于

<input type="text" :value="message" @input="(e) => {this.message = e.target.value}">

不同的表单控件绑定的事件总结

上面我们讲到v-model其实是一个语法糖,他本质包含了两个操作:

  • v-bind绑定了一个value属性
  • v-on指令给当前元素绑定对应事件,默认是input事件

原理说过了我们这里来整理一下,表单绑定v-model不同的控件到底是绑定了什么事件。

change事件

  • select
  • checkbox
  • radio

input事件

这是默认时间,当不是上面三种表单元素时,会解析成input事件,比如text、number等input元素和textarea

组件使用v-model

由上面的原理我们可以看出来组件上使用v-model也是类似的流程,本质上是子父组件通信的语法糖。看一个使用案例:

var child = {
    template: '<div><input type="text" :value="value" @input="emitEvent">{{value}}</div>',
    methods: {
      emitEvent(e) {
        this.$emit('input', e.target.value)
      }
    },
    props: ['value']
  }
 new Vue({
   data() {
     return {
       message: 'test'
     }
   },
   components: {
     child
   },
   template: '<div id="app"><child v-model="message"></child></div>',
   el: '#app'
 })

父组件上使用v-model,子组件默认会利用名为value的prop和名为input的事件。

源码这里就不详细说了,子组件的vnode会为data.props添加data.model.value,并且给data.on添加data.model.callback。因此父组件的语法糖本质上可以修改为

<child :value = "message" @input="function(e){message = e}"</child>

显然,这种写法就是事件通信的写法,这个过程又回到对事件指令的分析过程了。因此我们可以很明显的意识到,组件使用v-model本质上还是一个子父组件通信的语法糖。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • vue 如何绑定disabled属性让其不能被点击

    目录 vue绑定disabled属性让其不点击 vue disabled动态绑定事件 vue disabled的用法 vue绑定disabled属性让其不点击 vue disabled动态绑定事件 vue disabled的用法 <input type="text" :disabled="disabled"> data:{     disabled:false } 以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们.

  • 一文读懂vue动态属性数据绑定(v-bind指令)

    v-bind的基本用法 一.本节说明 前面的章节我们学习了如何向页面html标签进行插值操作,那么如果我们想动态改变html标签的属性,该怎么办呢? 这就是我们这节开始要讲的内容v-bind. 二. 怎么做 ":"为v-bind的简写形式,也可称为语法糖 三. 效果 四. 深入 在上图中将a标签的href属性值设置为toutiao,VUE实例将自动去data里面寻找toutiao属性进行值绑定. 不只是a标签,所有的html标签属性都可以通过v-bind进行值绑定,然后通过改变数据动态

  • Vue中通过属性绑定为元素绑定style行内样式的实例代码

    1.直接在元素上通过:style的形式,书写样式对象 <h1 :style="{color:'red','font-weight':200}">这是一个H1</h1> 2.将样式对象定义在data中,并直接引用到:style中 1:在data上定义样式 data:{ styleObj1:{color:'blue','font-weight':200,'font-size':'40px'}, } 2:在元素中,通过属性绑定的形式,将样式对象应用到元素中 <h

  • 关于vue中标签的属性绑定值渲染问题

    目录 标签的属性绑定值渲染问题 最终解决办法 vue标签属性条件渲染 1.v-bind 2.v-if和v-show的区别 标签的属性绑定值渲染问题 在项目中遇到一个棘手的问题,给span标签添加title,title显示的内容就是该span标签显示的内容,且返回的内容中有html标签,需要解析出来(考虑vue的slot插槽未能实现) 最终解决办法 在绑定的title中使用过滤器,去掉内容中的标签(本项目情况特殊,返回标签固定,所以替换标签比较方便),如果大家还有其他解决办法欢迎留言~~ //te

  • vue如何动态绑定img的src属性(v-bind)

    目录 动态绑定img的src属性(v-bind) 解决办法 vue添加img的src地址 v-bind 动态绑定img的src属性(v-bind) 今天遇到一个特别坑爹问题,页面中使用img动态绑定图片路径时总是不显示.(处理前的代码) <div class="prod-content"> <div class="prod-item" v-for="(item,index) in Merchant" :key="ind

  • Vue条件循环判断+计算属性+绑定样式v-bind的实例

    Vue.js条件与循环 1.条件判断 (1)v-if, <div id="app"> <p v-if="seen">现在你看到我了</p> <template v-if="ok"> <h1>菜鸟教程</h1> </template> </div> <script> new Vue({ el:'#app', data:{ seen:true,

  • vue动态绑定v-model属性名方式

    目录 vue动态绑定v-model属性名 1.目标 2.方案 vue双向绑定原理(v-model) 表单绑定 组件使用v-model vue动态绑定v-model属性名 1.目标 首先配置列,根据配置渲染表单,每个表单项绑定配置中的属性 2.方案 <template v-for="(item) in showQueryColumns" > <el-col :key="item.prop" :xs = "24" :sm = &qu

  • vue动态绑定class的几种常用方式小结

    本文实例讲述了vue动态绑定class的几种常用方式.分享给大家供大家参考,具体如下: 对象方法 最简单的绑定(这里的active加不加单引号都可以,以下也一样都能渲染) :class="{ 'active': isActive }" 判断是否绑定一个active :class="{'active':isActive==-1}" 或者 :class="{'active':isActive==index}" 绑定并判断多个 第一种(用逗号隔开) :

  • vue动态绑定ref(使用变量)以及获取方式

    目录 vue动态绑定ref及获取 一起来看下代码吧 ref三种使用方法 ref vue动态绑定ref及获取 正常情况,我们需要在vue中获得某个dom或者组件,我们会通过绑定 ref 然后通过绑定后的名字来获取这个dom . 但是,如果我们在v-for中绑定ref的话,那么这个ref就会存在多个,比如我们点击事件让对应的显示/隐藏的话,我们很难找到这个对应的元素. 那么,这时我们需要动态绑定不一样的ref(比如 Arr1.Arr2.Arr3这种),那么我们如何实现呢? 一起来看下代码吧 <div

  • vue正确使用watch监听属性变化方式

    目录 基本用法 监听object 使用deep参数 重新赋值 通过路径监听内部数据 初始化变量触发监听回调 总结 Vue中可以使用监听器监听属性的变化,并根据属性变化作出响应.但一旦涉及到复杂数据的监听(如Object,但数组一般不需要,因为Vue针对数组做了特殊处理)时就比较复杂了,本文解释了使用watch监听属性变化的方法,包括复杂数据. 基本用法 Vue watch最重要的使用场景是根据某属性的变化执行某些业务逻辑: <template>   <input type="n

  • 解决vue2.0动态绑定图片src属性值初始化时报错的问题

    在vue2.0中,经常会使用类似这样的语法 v-bind:src = " imgUrl "(缩写 :src = " imgUrl "),看一个案例 <template> <div> <img :src="imgUrl"> </div> </template> <script> export default { data(){ return { captcha_id: &quo

  • Vue组件中prop属性使用说明实例代码详解

    Prop 的大小写 (camelCase vs kebab-case) HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符.这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名: Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '<h

  • ant design vue中表格指定格式渲染方式

    注意点:定义的columns一定要写在data中,否则在加载过程中由于渲染顺序会导致其中的渲染函数无法识别 渲染方法1: 指定渲染函数: const columns = [ { title: '排名', dataIndex: 'key', customRender: renderContent // 渲染函数的规则 }, { title: '搜索关键词', dataIndex: 'keyword', customRender: (text, row, index) => { if (index

  • vue动态绑定图标的完整步骤

    0 图标和图片的不同 图标时字符,图片时二进制流.即图片加载会比图标慢,且加载图标最好不要用img标签,我们可以把图标当成组件用import的方法引入进来,然后当成标签引入. 1 安装svg 1.使用管理员身份运行cmd窗口,切换到项目目录下执行. npm add svg 2 从图标库下载图标 1.阿里图标库 https://www.iconfont.cn/ 2.下载svg 3.在compone目录下建立一个icons,在icons下建立一个svg目录,专门用来放图标. 3 查看插件的使用方法

  • 配置vue全局方法的两种方式实例

    目录 1,前言 2,第一种方式 3,第二种方式 总结 1,前言 在Vue项目开发中,肯定会有这样一个场景:在不同的组件页面用到同样的方法,比如格式化时间,文件下载,对象深拷贝,返回数据类型,复制文本等等.这时候我们就需要把常用函数抽离出来,提供给全局使用.那如何才能定义一个工具函数类,让我们在全局环境中都可以使用呢?请看下文分解. PS:本文vue为2.6.12 2,第一种方式 直接添加到Vue实例原型上 首先打开main.js,通过import引入定义的通用方法utils.js文件,然后使用V

  • 简单聊聊Vue中的计算属性和属性侦听

    目录 1. 计算属性 语法:  1.简写方式: 语法:  2.完整写法: 2. 监视(侦听)属性 1. 监视属性watch: 2. 深度监视 3. 区别和原则 总结 1. 计算属性 定义 计算属性:要用的属性不存在,要通过已有属性计算得来,计算属性要有一个全新的配置项computed 对Vue来说,data里面的数据就是属性,只要Vue中的数据改变,就会重新解析模板,遇到插值语法里的方法会重新调用 原理 底层借助了Objcet.defineproperty方法提供的getter和setter.

随机推荐