如何理解vue中的v-model?

如何理解vue中的v-model?_第1张图片

说到v-model,就想到了双向数据绑定,而且往往最常见的是在表单元素,源码分析

  • 源码分析
  • 源码分析
  • 自定义组件中的v-model源码分析
  • v-model的.lazy,.number,.trim修饰符源码分析
  • vue v-model源码分析

    vue如何获取v-mode的值(value)

    export default function model (
      el: ASTElement,
      dir: ASTDirective,
      _warn: Function
    ): ?boolean {
      warn = _warn
      const value = dir.value // vue通过这一行取到了v-model中的值,例如v-model="isChecked",v-model="inputValue"中isChecked和inputValue的值
    ...
    }
    
    const value = dir.value
    

    vue通过这一行取到了v-model中的值,例如v-model="isChecked",v-model="inputValue"中isChecked和inputValue的值。
    源码地址:web/compiler/directives/model.js 14~20行

    其中ASTDirective的interface定义如下:

    export interface ASTDirective {
      name: string;
      rawName: string;
      value: string; // 注意这里
      arg: string | undefined;
      modifiers: ASTModifiers | undefined;
    }
    

    源码地址:packages/vue-template-compiler/types/index.d.ts 80~86行

    用于生成各种类型model的gen*函数

      if (el.component) {
        genComponentModel(el, value, modifiers)
        // component v-model doesn't need extra runtime
        return false
      } else if (tag === 'select') {
        genSelect(el, value, modifiers)
      } else if (tag === 'input' && type === 'checkbox') {
        genCheckboxModel(el, value, modifiers)
      } else if (tag === 'input' && type === 'radio') {
        genRadioModel(el, value, modifiers)
      } else if (tag === 'input' || tag === '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 if (process.env.NODE_ENV !== 'production') {
        ...
      }
    

    其中包括gen*开头的生成model的各种函数,我们会在下文中进行分析。
    源码地址:web/compiler/directives/model.js 37~61行

    vue如何设置v-mode的值(value)

    genAssignmentCode函数(事件更新时,最终set v-model值的函数)

    // 自定义component组件
    const assignment = genAssignmentCode(value, valueExpression)
    // input(text),textarea
    let code = genAssignmentCode(value, valueExpression)
    // 
    addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
    // 源码分析 
     

    一句话概览:text和textarea标签的value属性和input事件及genDefaultModel函数。

    
    
    
    
    

    等价于:

    
    
    

    真的是这样吗?我们来看看源码。

    genDefaultModel函数

    function genDefaultModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
    ): ?boolean {
      const type = el.attrsMap.type
    
      // warn if v-bind:value conflicts with v-model
      // except for inputs with v-bind:type
      if (process.env.NODE_ENV !== 'production') {
         ...
      }
    
      const { lazy, number, trim } = modifiers || {}
      const needCompositionGuard = !lazy && type !== 'range'
      const event = lazy // 此处我们没有传入lazy修饰符,因此event变量是'input'
        ? 'change'
        : type === 'range'
          ? RANGE_TOKEN
          : 'input'
    
      let valueExpression = '$event.target.value' 
      if (trim) {
        valueExpression = `$event.target.value.trim()`
      }
      if (number) {
        valueExpression = `_n(${valueExpression})`
      }
    
      let code = genAssignmentCode(value, valueExpression)
      if (needCompositionGuard) {
        code = `if($event.target.composing)return;${code}`
      }
    
      addProp(el, 'value', `(${value})`) // 
      addHandler(el, event, code, null, true) // 这一步印证了input事件
      if (trim || number) {
        addHandler(el, 'blur', '$forceUpdate()')
      }
    }
    

    源码分析在源码中有注释。

    通过源码我们可以看出:
    input(type=“text”)和textarea的v-model,通过value prop获得值,最终被解析为设置value attribute和input(若设置lazy,则触发change)事件,从而实现双向绑定。

    源码地址:web/compiler/directives/model.js 127~147行

    源码分析

    一句话概览:input标签的checkbox和radio类型的checked属性和change事件及源码genCheckboxModel函数和genRadioModel函数。

    
    
    
    
    

    等价于:

    
    
    

    genCheckboxModel函数

    function genCheckboxModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
    ) {
      const number = modifiers && modifiers.number
      const valueBinding = getBindingAttr(el, 'value') || 'null' // 从v-bind的value获得到初始值,印证了
    
      const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
      const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
      addProp(el, 'checked', // 设置标签的checked attribute,印证了
        `Array.isArray(${value})` +
        `?_i(${value},${valueBinding})>-1` + (
          trueValueBinding === 'true'
            ? `:(${value})`
            : `:_q(${value},${trueValueBinding})`
        )
      )
      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
      )
    }
    

    genRadioModel函数

    function genRadioModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
    ) {
      const number = modifiers && modifiers.number
      let valueBinding = getBindingAttr(el, 'value') || 'null' // 印证了
    
      valueBinding = number ? `_n(${valueBinding})` : valueBinding
      addProp(el, 'checked', `_q(${value},${valueBinding})`)  // 设置标签的checked attribute,印证了
      addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true) // 这一步印证了
    }
    
    

    源码分析在源码中有注释。

    通过源码我们可以看出:
    input(type=“checkbox”)和input(type="radio")的v-model,通过value prop获得值,最终被解析为设置checked attribute和change事件,从而实现双向绑定。

    源码地址:web/compiler/directives/model.js 67~96行

    源码分析

    一句话概览:select标签的value属性和change事件及源码genSelect函数。

    
      
    

    等价于:

      
    

    genSelect 函数

    function genSelect (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
    ) {
      const number = modifiers && modifiers.number
      // 这一步印证了
      addHandler(el, 'change', code, null, true)
    }
    

    源码分析在源码中有注释。

    通过源码我们可以看出:
    select的v-model,通过value prop获得值,最终被解析为设置selected attribute和change事件,从而实现双向绑定。

    源码地址:web/compiler/directives/model.js 110~125行

    自定义组件中的v-model源码分析

    一句话概览:如何理解自定义组件中的v-model及genComponentModel,transformModel函数?

    其实不仅仅是input,textarea,和select这些常见的原生表单元素可以使用v-model,对于自定义的vue组件我们也可以使用v-model。

    Vue.component('base-checkbox', {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: Boolean
      },
      template: `
        
      `
    })
    
    
    

    lovingVue的值会传入checked这个prop,同时当组件触发一个change事件并且附带了一个新值的时候,lovingVue的值会被更新。

    如何从源码去理解这一段话吗?

    genComponentModel函数(构造出需要调用transformModel函数的el.model对象)

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

    源码地址:src/compiler/directives/model.js 6~31行
    重点关注el.model这一行。因为vue在创建自定义组件时,会对el.model这一行进行一些操作。

      // transform component v-model data into props & events
      if (isDef(data.model)) {
        transformModel(Ctor.options, data)
      }
    

    transformModel函数(转化组件v-model的value和callback到prop和event handler)

    // transform component v-model info (value and callback) into
    // prop and event handler respectively.
    function transformModel (options, data: any) {
      const prop = (options.model && options.model.prop) || 'value' //   model: {prop: 'checked',event:'change'} 中的”checked“作为prop
      const event = (options.model && options.model.event) || 'input' // model: {prop: 'checked',event:'change'} 中的”change“作为event
      ;(data.attrs || (data.attrs = {}))[prop] = data.model.value // lovingVue的值会传入checked这个prop
      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 // "同时当组件触发一个change事件并且附带了一个新值的时候,lovingVue的值会被更新。"
      }
    }
    

    源码地址:src/core/vdom/create-component.js 250~268行
    ”lovingVue的值会传入checked这个prop“的源码解释:
    data.attrs || (data.attrs = {}))[prop] = data.model.value
    获取到组价的v-model="lovingVue"中lovingVue的值,并且将v-model的longVue传入props的checked。若对如何获取v-model中的值有不理解,具体可以看上文第一部分vue v-model源码分析(vue如何获取v-mode的值(value))。

    "同时当组件触发一个change事件并且附带了一个新值的时候,lovingVue的值会被更新。"的源码解释:
    通过el.model = {... callback: }const callback = data.model.callback可以推断出:el.on.change = callback,而这个callback又是genAssignmentCode,在genAssignmentCode中会对v-model的值进行set。若是对如何set v-model的值有疑问,可以查看上文中的vue v-model源码分析(vue如何设置v-mode的值(value))。

    v-model的.lazy,.number,.trim修饰符源码分析

    const { lazy, number, trim } = modifiers || {}
    ...
      const event = lazy
        ? 'change'
        : type === 'range'
          ? RANGE_TOKEN
          : 'input'
    
      ...
      if (trim) {
        valueExpression = `$event.target.value.trim()`
      }
      if (number) {
        valueExpression = `_n(${valueExpression})`
      }
    
    • lazy仅仅是一个判断触发标签change还是input事件的标识符,lazy为true时,触发change
    • trim仅仅是调用了String.prototype.trim()
    • number这是vue的_n函数,而这个函数其实是一个StringToNumber函数,相当简单
    /**
     * Convert an input value to a number for persistence.
     * If the conversion fails, return original string.
     */
    export function toNumber (val: string): number | string {
      const n = parseFloat(val)
      return isNaN(n) ? val : n
    }
    

    源码地址:shared/util.js 97~100行

    参考资料:
    https://cn.vuejs.org/v2/guide/components.html#%E5%9C%A8%E7%BB%84%E4%BB%B6%E4%B8%8A%E4%BD%BF%E7%94%A8-v-model
    https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event
    https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event

    你可能感兴趣的:(如何理解vue中的v-model?)