概括

其实v-model是个语法糖。

v-model 常用于表单元素上进行数据的双向绑定,比如 <input>。除了原生的元素,它还能在自定义组件中使用。可以拆解为 props: value 和 events: input。就是说组件必须提供一个名为 value 的 prop,以及名为 input 的自定义事件,满足这两个条件,使用者就能在自定义组件上使用 v-model。比如下面的示例,实现了一个数字选择器:

  1. <template>
  2. <div>
  3. <button @click="increase(-1)">减 1</button>
  4. <span style="color: red;padding: 6px">{{ currentValue }}</span>
  5. <button @click="increase(1)">加 1</button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'InputNumber',
  11. props: {
  12. value: {
  13. type: Number
  14. }
  15. },
  16. data () {
  17. return {
  18. currentValue: this.value
  19. }
  20. },
  21. watch: {
  22. value (val) {
  23. this.currentValue = val;
  24. }
  25. },
  26. methods: {
  27. increase (val) {
  28. this.currentValue += val;
  29. this.$emit('input', this.currentValue);
  30. }
  31. }
  32. }
  33. </script>

props 一般不能在组件内修改,它是通过父级修改的,因此实现 v-model 一般都会有一个 currentValue 的内部 data,初始时从 value 获取一次值,当 value 修改时,也通过 watch 监听到及时更新;组件不会修改 value 的值,而是修改 currentValue,同时将修改的值通过自定义事件 input 派发给父组件,父组件接收到后,由父组件修改 value。所以,上面的数字选择器组件可以有下面两种使用方式:

  1. <template>
  2. <InputNumber v-model="value" />
  3. </template>
  4. <script>
  5. import InputNumber from '../components/input-number/input-number.vue';
  6. export default {
  7. components: { InputNumber },
  8. data () {
  9. return {
  10. value: 1
  11. }
  12. }
  13. }
  14. </script>

或:

  1. <template>
  2. <InputNumber :value="value" @input="handleChange" />
  3. </template>
  4. <script>
  5. import InputNumber from '../components/input-number/input-number.vue';
  6. export default {
  7. components: { InputNumber },
  8. data () {
  9. return {
  10. value: 1
  11. }
  12. },
  13. methods: {
  14. handleChange (val) {
  15. this.value = val;
  16. }
  17. }
  18. }
  19. </script>

如果你不想用 valueinput 这两个名字,从 Vue.js 2.2.0 版本开始,提供了一个 model 的选项,可以指定它们的名字,所以数字选择器组件也可以这样写:

  1. <template>
  2. <div>
  3. <button @click="increase(-1)">减 1</button>
  4. <span style="color: red;padding: 6px">{{ currentValue }}</span>
  5. <button @click="increase(1)">加 1</button>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'InputNumber',
  11. props: {
  12. number: {
  13. type: Number
  14. }
  15. },
  16. model: {
  17. prop: 'number',
  18. event: 'change'
  19. },
  20. data () {
  21. return {
  22. currentValue: this.number
  23. }
  24. },
  25. watch: {
  26. value (val) {
  27. this.currentValue = val;
  28. }
  29. },
  30. methods: {
  31. increase (val) {
  32. this.currentValue += val;
  33. this.$emit('number', this.currentValue);
  34. }
  35. }
  36. }
  37. </script>

在 model 选项里,就可以指定 prop 和 event 的名字了,而不一定非要用 value 和 input,因为这两个名字在一些原生表单元素里,有其它用处。