前言

在一次的学习过程中,偶然发现v-model的一个“奇怪”使用方法,如下图:
在这里插入图片描述
描述:

在一个组件中,引入一个自定义组件input.vue。然后需要在父组件中操作input的输入框内容。父组件(index.vue)的子组件实例上定义了v-model;input.vue组件中定义一个props,有一个value值,另外input标签的input事件绑定了一个事件名为input 的 $emit。

此示例中,我一直没有明白的地方有:

  • 父组件(index.vue)中input实例上的v-model如何响应?
  • 子组件(input.vue)中的props从哪里接受数据?
  • input标签的input事件中 $emit 在哪里监听?

因此下决心搞清楚v-model指令

表单输入框双向数据绑定

起初,v-model的作用是对应表单<input>、<textarea> 及 <select> 元素上创建双向数据绑定。Vue官方文档明确表示:

但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
那么实现原理如下

<template>
  <div class="hello">
    <h4>首先测试普通input输入框: {{text}}</h4>
    <input type="text" v-model="text"/>
    <input type="text" @input="val = $event.target.value" :value="val">
    <label>{{val}}</label>
  </div>
</template>

<script>
export default {
  name: 'index',
  data () {
    return {
      text: '',
      val: '112'
    }
  },
  components: {
    WInput
  }
}
</script>

组件中有两个input,一个使用v-model,另一个就是双向数据绑定的实际原理,使用input标签的input事件做实时监听。
刚刚的示例是基于input标签实现的,那么如果是其他的表单标签呢:

v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

示例如下:

<template>
  <div class="test">
    <div class="radio-group">
      <input type="radio" name="test" v-model="redioModel" value="one">
      <input type="radio" name="test" @change="redioChange" value="two">
    </div>
    <div class="checkbox-group">
      <input type="checkbox" value="1" v-model="checkboxVal">
      <input type="checkbox" value="2" v-model="checkboxVal">
      <input type="checkbox" value="3" @change="checkboxChange">
    </div>
    <div class="select-box">
      <!-- 使用 @change="selectChange" 替代v-model效果一样-->
      <select id="select" v-model="selectVal">
        <option value>请选择</option>
        <option value="1">test1</option>
        <option value="2">test2</option>
        <option value="3">test3</option>
        <option value="4">test4</option>
      </select>
    </div>
  </div>
</template>
<script>
export default {
  name: 'test',
  data () {
    return {
      redioModel: '',
      checkboxVal: [],
      selectVal: ''
    }
  },
  watch: {
    redioModel (newVal) {
      console.log(newVal)
    },
    checkboxVal (newVal) {
      console.log(newVal)
    },
    selectVal (newVal) {
      console.log(newVal)
    }
  },
  methods: {
    /**
     * radio v-modal等同事件
     */
    redioChange (e) {
      this.redioModel = e.target.value
    },
    /**
     * checkbox v-model等同事件
     */
    checkboxChange (e) {
      const checked = e.target.checked
      if (checked) {
        this.checkboxVal.push(e.target.value)
      } else {
        const index = this.checkboxVal.findIndex(
          item => item === e.target.value
        )
        this.checkboxVal.splice(index, 1)
      }
    },
    selectChange (e) {
      this.selectVal = e.target.value
    }
  }
}
</script>

单选框radio、复选框CheckBox、下拉窗select,其中都是用了v-modal 和事件change两种实现双重绑定的方式。效果相同。

自定义组件的 v-model

这是Vue2.2的新增,官方解释:

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突

这句定义已经很好地解释了文章一开始提出的疑问
意思就是在自定义组件上使用v-modal就相当于:

<template>
  <div class="hello">
    <h4>首先测试普通input输入框: {{text}}</h4>
    <w-input v-model="text"/>
    <!-- 上行等价于下行 -->
    <w-input @input="value => val = value" :value="val"/>
    <label>{{val}}</label>
  </div>
</template>

<script>
import WInput from './input'
export default {
  name: 'index',
  data () {
    return {
      text: '',
      val: '123'
    }
  },

同理:
对于自定义组件中不同的响应的输出元素,也应该对应的抛出不同事件(同上):

  • text 和 textarea 元素使用 value 属性和 input 事件;
  • checkbox 和 radio 使用 checked 属性和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。
// index.vue
<template>
  <div class="hello">
    <w-input v-model="changes"/>
    <h4>测试选中: {{changes ? '选中': '未选中'}}</h4>
  </div>
</template>

<script>
import WInput from './input'
export default {
  name: 'index',
  data () {
    return {
      changes: true
    }
  }
 .....
// input.vue
 <template>
  <input type="checkbox" :checked="value" @change="handleInput">
</template>
<script>
export default {
  name: 'w-input',
  props: {
    value: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.checked)
    }
  }
}

总结

很多时候看Vue文档,对立面的一些方法属性,知其然不知其所以然,定义说的比较模糊,也不知道实际作用,应该怎么用,用在什么地方。只有不断地写项目,不断地尝试新的东西,才能活学活用

Logo

前往低代码交流专区

更多推荐