Vue之v-model解析

前言

今天介绍Vue.js中一种常用的指令:v-model,以及v-model指令特性带来的一些使用。

具体分析

依旧是以简单实例为引去分析Vue.js中针对v-model的处理,具体实例:

<input v-model="text" />
<script>
    new Vue({
        data: {
            text: '测试v-model'
        }
    })
script>

实际上v-model的处理还需要从render函数的构建来说,即baseCompile来完成ast树的构建。

parse解析v-model

实际上这部分构建v-model在ast结构中的具体结构,具体的处理逻辑如下:

Vue之v-model解析_第1张图片

Vue源码中对于v-model在静态解析阶段,实际上分为两部分:

  • 解析出v-model部分
  • 针对v-model指令的特殊处理

首先第一点:解析出v-model部分,此处的处理与Vue.js中标签属性是相同的处理,即v-model作为属性名,就实例而言,即{‘v-model’: text}

第二点:v-model指令特殊处理,v-model虽然是标签的属性,但是是Vue.js中的指令,此时就是上面逻辑中给出的,实际上最后会调用processAttrs来处理

processAttrs

下面就来看看针对v-model,这边相关的处理逻辑如下:

Vue之v-model解析_第2张图片

从上面的逻辑过程中可知:

processAttrs实际上处理三种特殊的属性:指令、事件绑定、prop

而针对指令的相关处理实际上主要就是调用:addDirective,该函数的作用就是构建指令对象并创建directives对象,即:

el.directives.push({
    // 不带v-前缀的指令名
    name,
    // 完整名称
    rawName,
    // 指令的绑定值
    value,
    // 传递给指令的参数
    arg,
    // 一个包含修饰符的对象
    modifiers
});

generate中处理v-model部分

generate函数实际上就是将parse出来的code解析成指定结构的AST。

就以实例来说,在parse阶段实际上v-model实际上构建成了如下的结构并保存在directives中:

{
    arg: null
	modifiers: undefined
	name: "model"
	rawName: "v-model"
	value: "text"
}

而在generate这个阶段的处理,主要逻辑如下:

Vue之v-model解析_第3张图片

上图中的主要逻辑都是建立在实例基础上的,实际上genElement中还有其他的逻辑针对不同情况,但是不是本篇要讨论的。实际的处理实际上会调用genData$2函数,而genData$2中关键的处理是genDirectives。

genDirectives中会调用model函数,而该函数实际上是针对不同的标签来进行处理,主要的处理逻辑如下:

Vue之v-model解析_第4张图片

对于标签是input的v-model的处理,实际上会调用getDefaultModel函数,该函数就是处理v-model成特定的结构,实际上还部分的逻辑主要就两点:

  • 将v-model绑定的值作为prop

  • 定义相关的事件,即v-model

第一点:调用addProp函数

(el.props || (el.props = [])).push({ name: name, value: value });

第二点:定义事件,如果modifiers中定义了lazy就会定义change事件,否则定义input事件

// 定义el.events
var events = el.events || (el.events = {});
// 这里兼容了多个相同事件的定义,如果用一个事件存在多个实际上这里就会保存为数组
/*
	这里的code实际上对于当前实例则是
	if($event.target.composing) return;
    text=$event.target.value
*/
events[name] = { value: code}

之后的流程处理就回到了genData$2中了,而上面逻辑的定义props和event则会在接下来的逻辑处理中进行相关处理。

// v-model中定义props会在这里处理
if (el.props) {
   data += "domProps:{" + (genProps(el.props)) + "},";
}
// v-model定义events会在这里处理
if (el.events) {
   data += (genHandlers(el.events, false, state.warn)) + ",";
}

经历过generate的处理,实际上构建出Vue.js指定的结构了,就以实例而言,v-model所在标签构建的结构如下:

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

上面的处理是基于实例的基础的逻辑,而实际上model函数针对标签的类型有不同的处理逻辑,主要类型分为:

  • 子组件
  • select标签
  • checkbox标签
  • radio标签
  • input或textarea标签
  • 非HTML标签或SVG标签

子组件v-model

子组件会调用genComponentModel函数来处理,主要的处理逻辑是定义model属性,即:

  el.model = {
    value,
    expression,
    callback,
  };

而在genData$2中处理如下:

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

非html或SVG标签也是调用该函数进行相关处理。

select标签v-model

调用genSelect函数,定义change事件:

  var number = modifiers && modifiers.number;
  // 从这里可以看出v-model背后的主要处理了
  var selectedVal = "Array.prototype.filter" +
    ".call($event.target.options,function(o){return o.selected})" +
    ".map(function(o){var val = \"_value\" in o ? o._value : o.value;" +
    "return " + (number ? '_n(val)' : 'val') + "})";

  var assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]';
  var code = "var $$selectedVal = " + selectedVal + ";";
  code = code + " " + (genAssignmentCode(value, assignment));
  // 定义events属性
  addHandler(el, 'change', code, null, true);

checkbox和radio标签v-model

这两种标签主要的处理逻辑基本相同(但是不是同一个函数),定义checked属性和change事件,主要的处理还是change中获取当前状态

v-model使用姿势

从上面整个分析,v-model在编辑过程的结构就比较清晰了,下面列举下场使用子组件更新prop内容的几种方式以及model修改:

  • 在子组件中常使用input更新父组件中传递的prop:

    function(e) { this.$emit('input', e); }
    
  • 在子组件中修改v-model的默认事件,提供修改v-model的默认处理

    model: {
        prop: 'checked',
        event: 'change'
    },
    props: {
        value: String,
        checked: {
          type: Number,
          default: 0
        }
    }
    

再看上面的一些使用,估计就很清晰了,实际上Vue.js也提供sync修饰符 + update事件来实现子组件更新prop值。

你可能感兴趣的:(Vue相关)