v-model 是 vue 中进行数据双向绑定的指令,在内部实际上是通过语法糖来完成数据的双向绑定,v-model 绑定的形式有两种,一种是绑定在普通表单元素上,一种是绑定在自定义组件上,两者在实现上也略微不同;

绑定在普通表单元素上,分别有 input select textarea radio 等,从源码中也可以看出,他们之间的实现也略微不同,区别在于改变值时触发的事件不同;

一、普通表单元素

select、checkbox、radio 语法糖对应的是 v-bind:value="something"v-on:change="something = $event.target.value";在源码进行指令解析时会给 el 绑定事件,分别如下:

// src/platforms/web/compiler/directives/model.js
// select
addHandler(el, 'change', code, null, true)
// checkbox
addHandler(el, 'change',
           `var $$a=${value},` +
           '$$el=$event.target,' +
           `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
           'if(Array.isArray($$a)){' +
           `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
           '$$i=_i($$a,$$v);' +
           `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +
           `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +
           `}else{${genAssignmentCode(value, '$$c')}}`,
           null, true
)
// radio
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)

addHandler 是绑定事件的方法,第二个参数是绑定事件的名称,可以看出, selectcheckboxradio是绑定的 change 事件,也就是值改变时会触发 change 事件;

input、textarea 对应的语法糖有几种情况:

  • 默认绑定事件为 input 事件;
  • 如果在 v-model 绑定时用了 lazy 修饰符,那么它绑定的事件是 change
  • 如果有 type="range"属性,则绑定的事件是 __r
  • 如果有 trim 或者 number 修饰符,则会多绑定一个 blur 事件;
// src/platforms/web/compiler/directives/model.js
export const RANGE_TOKEN = '__r'
const event = lazy
    ? 'change'
    : type === 'range'
      ? RANGE_TOKEN
      : 'input'
// ...其他代码
addHandler(el, event, code, null, true)
if (trim || number) {
    addHandler(el, 'blur', '$forceUpdate()')
}

二、自定义组件

v-model 绑定在自定义组件时,调用的是 genComponentModel 方法,该方法主要的目的是在 el 身上绑定一个 model 对象,对象包括 valuecallbackexpression三个属性;

export function genComponentModel (
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
    // ...
    el.model = {
        value: `(${value})`,
        expression: JSON.stringify(value),
        callback: `function (${baseValueExpression}) {${assignment}}`
    }
}

在创建组件会判断是否有 model选项,有则调用 transformModel进行处理;

// src/core/vdom/create-component.js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
	// ...其他代码
    if (isDef(data.model)) {
        transformModel(Ctor.options, data)
	}
}

function transformModel (options, data: any) {
    // 有自定义的 model 选项,则使用自定义,否则默认语法糖为 value / input
    const prop = (options.model && options.model.prop) || 'value'
    const event = (options.model && options.model.event) || 'input'
    ;(data.attrs || (data.attrs = {}))[prop] = data.model.value
    const on = data.on || (data.on = {})
    const existing = on[event]
    const callback = data.model.callback
    if (isDef(existing)) {
        if (
            Array.isArray(existing)
            ? existing.indexOf(callback) === -1
            : existing !== callback
        ) {
            on[event] = [callback].concat(existing)
        }
    } else {
        on[event] = callback
    }
}

transformModel 主要是确定自定义组件的语法糖,如果自定义组件的 options 里有 model 选项,则使用 model 选项里面对应的属性作为语法糖,如果没有 model 选项,则默认语法糖是 v-bind:value v-on:input

<!-- 自定义组件 -->
<script>
export default {
    name: 'customCom',
    props: {
        // v-model 绑定的值,默认应该为 value
        // 但是多了 model 选项中的 prop,所以可以通过 this.customValue 获取
      	customValue: {}  
    },
    model: {
        // 自定义 v-model 绑定的别名,必须有,否则依旧是 value 命名
        prop: 'customValue',
        // 修改绑定值时触发的事件
        event: 'customEvent'
    }
}
</script>

三、总结:

  • v-model 绑定在普通表单元素 selectcheckboxradio 时,语法糖为 v-bind:valuev-on:change
  • v-model 绑定在 inputtextarea时,语法糖分几种情况:
    • 默认为 input 事件;
    • lazy 修饰符时为 change 事件;
    • type="range" 属性时为 __r
    • trimnumber 时新增 blur 事件;
  • v-model 绑定在自定义组件时,语法糖为 v-bind:valuev-on:input 或者自定义 model 选项;

可以通过在线模板编译来验证一下 v-model 的语法糖;

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐