Skip to content

v-model

Базовое использование

v-model можно использовать в компоненте для реализации двустороннего связывания.

Начиная с Vue 3.4, для достижения этих целей рекомендуется использовать макрос defineModel():

vue
<!-- Child.vue -->
<script setup>
const model = defineModel()

function update() {
  model.value++
}
</script>

<template>
  <div>Parent bound v-model is: {{ model }}</div>
</template>

Далее родитель может связать значение с помощью v-model:

template
<!-- Parent.vue -->
<Child v-model="countModel" />

Значение, возвращаемое функцией defineModel(), представляет собой ref. К ней можно получить доступ и изменить так же, как и любую другую ref, за исключением того, что она действует как двустороннее связывание между родительским значением и локальным значением:

  • Значение .value синхронизировано со значением, связанным с родительским v-model;
  • Когда оно изменяется дочерним элементом, это приводит к обновлению значения, связанного с родителем.

Это означает, что вы также можете связать этот ref к нативному полю ввода с помощью v-model, что делает оборачивание элементов ввода простым и обеспечивает аналогичное использование v-model:

vue
<script setup>
const model = defineModel()
</script>

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

Попробовать в песочнице

Под капотом

defineModel - это удобный макрос. Компилятор раскрывает его в следующее:

  • Свойство с именем modelValue, значение которого синхронизировано со значением локальной ref;
  • Событие с именем update:modelValue, которое возникает при изменении значения локальной ref.

Вот как вы бы реализовали тот же дочерний компонент, который был показан выше до версии 3.4:

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

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

Затем v-model="modelValue" в родительском компоненте будет скомпилирован в:

template
<!-- Parent.vue -->
<Child
  :modelValue="foo"
  @update:modelValue="$event => (foo = $event)"
/>

Как вы можете видеть, это требует чуть больше кода. Тем не менее, полезно знать, что происходит под капотом.

Поскольку defineModel объявляет входные параметры, вы можете также объявить входные параметры основного свойства, передав его в defineModel:

js
// делает v-model обязательным
const model = defineModel({ required: true })

// установка значения по умолчанию
const model = defineModel({ default: 0 })

Предупреждение

Если у вас есть значение default для свойства defineModel, и вы не предоставляете никакого значения для этого свойства из родительского компонента, это может привести к рассинхронизации между родительским и дочерним компонентами. В приведенном ниже примере родительский myRef не определен, а дочерний model равен 1:

js
// дочерний компонент:
const model = defineModel({ default: 1 })

// родительский компонент:
const myRef = ref()
html
<Child v-model="myRef"></Child>

Давайте сначала рассмотрим, как v-model используется в нативном элементе:

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

Под капотом компилятор шаблонов расширяет v-model до более подробного эквивалента для нас. Таким образом, приведенный выше код выполняет то же самое, что и следующий:

template
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

При использовании на компоненте v-model расширяется до следующего:

template
<CustomInput
  :model-value="searchText"
  @update:model-value="newValue => searchText = newValue"
/>

Для того чтобы это действительно работало, компонент <CustomInput> должен выполнить две вещи:

  1. Привязать атрибут value элемента <input> к свойству modelValue
  2. При срабатывании события input элемента <input> генерировать событие update:modelValue с новым значением

Вот это в действии:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

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

Теперь v-model должен отлично работать с этим компонентом:

template
<CustomInput v-model="searchText" />

Попробовать в песочнице

Другой способ реализации v-model в этом компоненте - использовать вычисляемое свойство computed с геттером и сеттером. Метод get должен возвращать свойство modelValue, а метод set должен вызывать соответствующее событие:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

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

Аргументы v-model

v-model может принимать аргумент:

template
<MyComponent v-model:title="bookTitle" />

В дочернем компоненте мы можем поддерживать соответствующий аргумент, передав строку в defineModel() в качестве его первого аргумента:

vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
</script>

<template>
  <input type="text" v-model="title" />
</template>

Попробовать в песочнице

Если необходимо передать входные параметры, то их следует передавать после имени модели:

js
const title = defineModel('title', { required: true })
Использование до версии 3.4
vue
<!-- MyComponent.vue -->
<script setup>
defineProps({
  title: {
    required: true
  }
})
defineEmits(['update:title'])
</script>

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

Попробовать в песочнице

В этом случае, вместо стандартного свойства modelValue и события update:modelValue, дочерний компонент должен ожидать свойство title и генерировать событие update:title для обновления значения в родительском компоненте:

vue
<!-- MyComponent.vue -->
<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

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

Попробовать в песочнице

Множественные привязки v-model

Используя возможность указывать конкретное свойство и событие, как мы узнали ранее с аргументами v-model, теперь мы можем создать несколько v-model на одном экземпляре компонента.

Каждый v-model будет синхронизироваться с разным свойством, без необходимости в дополнительных параметрах в компоненте:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

Попробовать в песочнице

Использование до версии 3.4
vue
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

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

Попробовать в песочнице

vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName']
}
</script>

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

Попробовать в песочнице

Работа с модификаторами v-model

Когда мы изучали привязки ввода формы, мы видели, что v-model имеет встроенные модификаторы - .trim, .number и .lazy. В некоторых случаях вам также может потребоваться, чтобы v-model в вашем компоненте ввода поддерживал пользовательские модификаторы.

Давайте создадим пример пользовательского модификатора capitalize, который преобразует в верхний регистр первую букву строки, предоставленной привязкой v-model:

template
<MyComponent v-model.capitalize="myText" />

Модификаторы, добавленные в компонент v-model, могут быть доступны в дочернем компоненте с помощью деструктуризации возвращаемого значения defineModel() следующим образом:

vue
<script setup>
const [model, modifiers] = defineModel()

console.log(modifiers) // { capitalize: true }
</script>

<template>
  <input type="text" v-model="model" />
</template>

Чтобы условно настроить чтение / запись значения на основе модификаторов, мы можем передать опции get и set в defineModel(). Эти две опции получают значение при получении / установке ref на модель и должны возвращать преобразованное значение. Вот как мы можем использовать set для реализации модификатора capitalize:

vue
<script setup>
const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})
</script>

<template>
  <input type="text" v-model="model" />
</template>

Попробовать в песочнице

Использование до версии 3.4
vue
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

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

function emitValue(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 type="text" :value="modelValue" @input="emitValue" />
</template>

Попробовать в песочнице

Модификаторы, добавленные в компонент v-model, будут предоставляться компоненту через свойство modelModifiers. В приведенном ниже примере мы создали компонент, содержащий свойство modelModifiers, которое по умолчанию содержит пустой объект:

vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

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

Обратите внимание, что свойство компонента modelModifiers содержит capitalize и его значение - true, потому что оно установлено в связке v-model как v-model.capitalize="myText".

Теперь, когда у нас есть настроенное свойство, мы можем проверить ключи объекта modelModifiers и написать обработчик для изменения переданного значения. В следующем коде мы преобразуем строку в верхний регистр при каждом срабатывании события input элемента <input />:

vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Попробовать в песочнице

Модификаторы с аргументами для v-model

Для привязок v-model с аргументами и модификаторами, сгенерированное имя входного параметра будет arg + "Modifiers". Например:

template
<MyComponent v-model:title.capitalize="myText">

Соответствующие объявления должны быть следующими:

js
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}

Вот еще один пример использования модификаторов с несколькими v-model и с разными аргументами:

template
<UserName
  v-model:first-name.capitalize="first"
  v-model:last-name.uppercase="last"
/>
vue
<script setup>
const [firstName, firstNameModifiers] = defineModel('firstName')
const [lastName, lastNameModifiers] = defineModel('lastName')

console.log(firstNameModifiers) // { capitalize: true }
console.log(lastNameModifiers) // { uppercase: true }
</script>
Использование до версии 3.4
vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])

console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true }
</script>
vue
<script>
export default {
  props: {
    firstName: String,
    lastName: String,
    firstNameModifiers: {
      default: () => ({})
    },
    lastNameModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:firstName', 'update:lastName'],
  created() {
    console.log(this.firstNameModifiers) // { capitalize: true }
    console.log(this.lastNameModifiers) // { uppercase: true }
  }
}
</script>
v-modelУже загружено