在 Vue 中可以使用 v-model 指令来用作对表单数据的双向绑定。此指令的使用仅局限于以下几种情况:

  • <input>
  • <textarea>
  • <select>
  • components

可以使用以下几种修饰符:

  • .lazy 使用 change 事件而非 input
  • .number 将输入的字符串转为数字
  • .trim 将输入框的内容进行 trim 操作

在表单中使用

这个指令其实是一种语法糖

  1. <template>
  2. <input :value="inputValue" @input="$emit('input', inputValue = $event.target.value)" />
  3. </template>
  4. <script>
  5. import { ref } from 'vue'
  6. export default {
  7. name: 'Foo',
  8. setup() {
  9. const inputValue = ref('')
  10. return {
  11. inputValue
  12. }
  13. }
  14. }
  15. </script>
  1. <template>
  2. <input v-model="inputValue" />
  3. </template>
  4. <script>
  5. import { ref } from 'vue'
  6. export default {
  7. name: 'Foo',
  8. setup() {
  9. const inputValue = ref('')
  10. return {
  11. inputValue
  12. }
  13. }
  14. }
  15. </script>

在组件中使用

常规使用方式

父级组件

  1. <template>
  2. <custom-input
  3. :model-value="searchText"
  4. @update:model-value="searchText = $event"
  5. ></custom-input>
  6. <custom-input v-model="searchText" />
  7. </template>

子组件

  1. <template>
  2. <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"
  3. </template>

或者使用计算属性,将 value 设置为 getter 和 setter

  1. app.component('custom-input', {
  2. props: ['modelValue'],
  3. emits: ['update:modelValue'],
  4. template: `
  5. <input v-model="value">
  6. `,
  7. computed: {
  8. value: {
  9. get() {
  10. return this.modelValue
  11. },
  12. set(value) {
  13. this.$emit('update:modelValue', value)
  14. }
  15. }
  16. }
  17. })

创建新的响应式数据来模拟

父级组件:

  1. <template>
  2. <child v-model:myData="text"></child>
  3. <p>{{ text }}</p>
  4. </template>
  5. <script setup>
  6. import Child from './Child.vue'
  7. import { ref } from 'vue'
  8. const props = defineProps({
  9. msg: String
  10. })
  11. const text = ref('')
  12. </script>

子级组件:

  1. <template>
  2. <div>
  3. <input type="text" v-model="source">
  4. </div>
  5. </template>
  6. <script setup>
  7. import { defineProps, watch, ref } from 'vue'
  8. const props = defineProps({
  9. myData: String
  10. })
  11. const emit = defineEmits(['update:myData'])
  12. const source = ref(props.myData)
  13. watch(source, () => {
  14. emit('update:myData', source.value)
  15. })
  16. </script>

抽离成 hooks

将其抽象为一个单独的自定义 hooks:

  1. // useVModel.ts
  2. import { computed, getCurrentInstance } from 'vue'
  3. export function useVModel(props, name, emit) {
  4. const cEmit = emit || getCurrentInstance()?.emit
  5. return computed({
  6. get() {
  7. return props[name]
  8. },
  9. set(v) {
  10. if (cEmit) {
  11. emit(`update:${name}`, v)
  12. }
  13. }
  14. })
  15. }

这样的话就可以在自组件中直接使用 v-model也可以实现数据在多级组件中传递(parent -> child -> grandChild),可以进行三级或以上的参数传递

  1. // 父级组件
  2. <template>
  3. <child v-model:myData="counter" />
  4. </template>
  5. // 子组件
  6. <template>
  7. <input type="text" v-model="data" />
  8. </template>
  9. <script lang="ts">
  10. import { useVModel } from './hooks/useVModel'
  11. import { defineComponent } from 'vue'
  12. export default defineComponent({
  13. props: {
  14. myData: String
  15. },
  16. setup(props, { emit }) {
  17. const data = useVModel(props, 'myData', emit)
  18. return {
  19. data
  20. }
  21. }
  22. })
  23. </script>

多层传递

  1. <!-- 父级组件 -->
  2. <template>
  3. <div>
  4. <h2>Parent</h2>
  5. <p>val: {{ data }}</p>
  6. <button @click="name += counter">set title</button>
  7. <br />
  8. <child v-model:myData="data" />
  9. </div>
  10. </template>
  11. <script setup lang="ts">
  12. import { ref, watch } from 'vue'
  13. import Child from './child.vue'
  14. const data = ref('hello')
  15. watch(data, (val) => {
  16. console.log(val)
  17. })
  18. </script>
  19. <!-- 子级组件 -->
  20. <template>
  21. <div>
  22. <grand-child v-model:myData="data"></grand-child>
  23. </div>
  24. </template>
  25. <script lang="ts">
  26. import { defineComponent } from 'vue'
  27. import GrandChild from './grandChild.vue'
  28. import { useVModel } from './hooks/useVModel'
  29. export default defineComponent({
  30. name: 'Child',
  31. props: {
  32. myData: String
  33. },
  34. components: {
  35. GrandChild
  36. },
  37. setup(props, { emit }) {
  38. const data = useVModel(props, 'myData', emit)
  39. return {
  40. data
  41. }
  42. }
  43. })
  44. </script>
  45. <!-- 孙级组件 -->
  46. <template>
  47. <div>
  48. <input type="text" v-model:myData="data">
  49. </div>
  50. </template>
  51. <script lang="ts">
  52. import { useVModel } from './hooks/useVModel'
  53. import { defineComponent } from 'vue'
  54. export default defineComponent({
  55. name: 'grandChild',
  56. props: {
  57. myData: String
  58. },
  59. setup(props, { emit }) {
  60. const data = useVModel(props, 'myData', emit)
  61. return {
  62. data
  63. }
  64. }
  65. })
  66. </script>

使用多个 v-model 绑定

  1. <user-name
  2. v-model:first-name="firstName"
  3. v-model:last-name="lastName"
  4. ></user-name>
  1. app.component('user-name', {
  2. props: {
  3. firstName: String,
  4. lastName: String
  5. },
  6. emits: ['update:firstName', 'update:lastName'],
  7. template: `
  8. <input
  9. type="text"
  10. :value="firstName"
  11. @input="$emit('update:firstName', $event.target.value)" />
  12. <input
  13. type="text"
  14. :value="lastName"
  15. @input="$emit('update:lastName', $event.target.value)" />
  16. `
  17. })