v-model

双向绑定怎么实现的?

本质

v-model 是语法糖,本质是 props + emit

父组件 子组件 │ │ │ :modelValue="count" │ │───────────────────────>│ props: ['modelValue'] │ │ │ @update:modelValue │ │<───────────────────────│ emit('update:modelValue', val) │ │

等价的写法

<Child v-model="count" />

<!-- 等价于 -->
<Child :modelValue="count" @update:modelValue="count = $event" />

Vue 2

默认绑定 value + input

// Child.vue
props: ['value'],
methods: {
  update(val) {
    this.$emit('input', val)
  }
}

.sync 修饰符

Vue 2 用 .sync 实现多值双向绑定:

<!-- Parent.vue -->
<Child :title.sync="title" :name.sync="name" />

<!-- 会被展开为 -->
<Child
  :title="title" @update:title="title = $event"
  :name="name" @update:name="name = $event"
/>

Vue 3

统一了 API,不再需要 .sync

基本用法

<!-- Child.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

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

计算属性实现(更简洁)

<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(val) {
    emit('update:modelValue', val)
  }
})
</script>

<template>
  <input v-model="value" />
</template>

多个 v-model

<!-- Parent.vue -->
<template>
  <UserName
    v-model:first="firstName"
    v-model:last="lastName"
  />
</template>

<script setup>
import { ref } from 'vue'
const firstName = ref('Tom')
const lastName = ref('Jerry')
</script>
<!-- UserName.vue -->
<script setup>
defineProps(['first', 'last'])
defineEmits(['update:first', 'update:last'])
</script>

<template>
  <input
    :value="first"
    @input="$emit('update:first', $event.target.value)"
  />
  <input
    :value="last"
    @input="$emit('update:last', $event.target.value)"
  />
</template>

自定义修饰符

内置修饰符

<!-- trim: 自动去除首尾空格 -->
<input v-model.trim="text" />

<!-- number: 自动转数字 -->
<input v-model.number="num" />

<!-- lazy: 失焦时触发 -->
<input v-model.lazy="text" />

自定义修饰符

<!-- Parent.vue -->
<template>
  <Input v-model.capitalize="text" />
</template>

<script setup>
import { ref } from 'vue'
const text = ref('hello')
</script>
<!-- Input.vue -->
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: {
    default: () => ({})
  }
})

const emit = defineEmits(['update:modelValue'])

function handleInput(e) {
  let value = e.target.value

  // 应用自定义修饰符
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }

  emit('update:modelValue', value)
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

总结

Vue 2Vue 3
PropvaluemodelValue
Eventinputupdate:modelValue
多值.sync多个 v-model
修饰符无内置trim/number/lazy + 自定义