zoukankan      html  css  js  c++  java
  • 如何理解vue中的v-model?

    说到v-model,就想到了双向数据绑定,而且往往最常见的是在表单元素<input>,<textarea>,<select>中的使用。

    那么为什么v-model双向数据绑定,自动更新元素呢?

    v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
    text 和 textarea 元素使用 value 属性和 input 事件;
    checkbox 和 radio 使用 checked 属性和 change 事件;
    select 字段将 value 作为 prop 并将 change 作为事件。

    因此我们将来好好探讨一下:

    • vue v-model源码分析
    • text和textarea标签的value属性和input事件及源码genDefaultModel函数
    • input标签的checkbox和radio类型的checked属性和change事件及源码genCheckboxModel函数和genRadioModel函数
    • select标签的value属性和change事件及源码genSelect函数
    • v-model的.lazy,.number,.trim修饰符源码分析

    vue v-model源码分析

      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') {
        ...
      }

    源码地址:web/compiler/directives/model.js 37~61行

    text和textarea标签的value属性和input事件及genDefaultModel函数

    <!--text类型-->
    <input v-model="singleMsg">
    <!--textarea类型-->
    <textarea v-model="multiMsg"></textarea>

    等价于:

    <input type="text" v-bind:value="singleMsg" v-on:input="singleMsg=$event.target.value" >
    <textarea v-bind:value="multiMsg" v-on:input="multiMsg=$event.target.value"></textarea>

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

    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})`) // <input type="text" v-bind:value="singleMsg" ="singleMsg=$event.target.value" >
      addHandler(el, event, code, null, true) // 这一步印证了input事件<input v-on: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函数

    <!--checkbox类型-->
    <input type="checkbox" v-model="checkboxCtrl">
    <!--input类型-->
    <input type="radio" v-model="radioCtrl">

    等价于:

    <input type="checkbox" v-bind:value="checkboxCtrl" v-on:change="checkboxCtrl=$event.target.checked">
    <input type="radio" v-bind:value="radioCtrl" v-on:change="radioCtrl=$event.target.checked">

    genCheckboxModel函数

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

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

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

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

    select标签的value属性和change事件及源码genSelect函数

    <!--select类型-->
      <select v-model="selectCtrl">
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>

    等价于:

      <select v-bind:value="selectValue" v-on:change="selectValue=$event.target.options.selected">
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>

    genSelect 函数

    function genSelect (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
    ) {
      const number = modifiers && modifiers.number
      // 这一步印证了<select v-bind:value="selectValue" ...="selectValue=$event.target.options.selected">
      const 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'}})` 
    
      const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
      let code = `var $$selectedVal = ${selectedVal};`
      code = `${code} ${genAssignmentCode(value, assignment)}`
      // 这一步印证了 <select v-on:change="...">
      addHandler(el, 'change', code, null, true)
    }

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

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

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

    资源搜索网站大全https://55wd.com 广州品牌设计公司http://www.maiqicn.com

    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
    }
  • 相关阅读:
    第五课 主引导程序的扩展 下
    C.Candy
    B.大钉骑马走江湖
    A喝酒(北京林业大学校赛)
    HDU 5666 Segment
    南京理工大学第八届校赛题目题解(部分)
    TCO 2016 Round 1B
    139. Word Break
    90. Subsets II
    78. Subsets
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/13765067.html
Copyright © 2011-2022 走看看