Hack Frontend Community

v-model в Vue.js

Что такое v-model?

v-model — это директива Vue, которая создаёт двустороннее связывание данных (two-way binding) между элементом формы и данными компонента.

<template>
  <input v-model="message" />
  <p>Сообщение: {{ message }}</p>
</template>

<script setup>
import { ref } from 'vue'

const message = ref('Привет')
</script>

Когда пользователь вводит текст, message обновляется автоматически, и наоборот.


Как работает v-model?

v-model — это синтаксический сахар над связыванием значения и обработкой события:

<!-- С v-model -->
<input v-model="text" />

<!-- Эквивалентно -->
<input 
  :value="text"
  @input="text = $event.target.value"
/>

Vue автоматически:

  1. Связывает значение с данными (:value)
  2. Слушает событие изменения (@input)
  3. Обновляет данные при изменении

v-model с разными элементами

Input (text)

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

<script setup>
import { ref } from 'vue'
const text = ref('')
</script>

Textarea

<template>
  <textarea v-model="message"></textarea>
  <p>{{ message }}</p>
</template>

<script setup>
import { ref } from 'vue'
const message = ref('Многострочный текст')
</script>

Checkbox (один элемент)

<template>
  <input type="checkbox" v-model="checked" />
  <span>Согласен: {{ checked }}</span>
</template>

<script setup>
import { ref } from 'vue'
const checked = ref(false)
</script>

Checkbox (несколько элементов)

<template>
  <input type="checkbox" value="Vue" v-model="frameworks" />
  <input type="checkbox" value="React" v-model="frameworks" />
  <input type="checkbox" value="Angular" v-model="frameworks" />
  <p>Выбрано: {{ frameworks }}</p>
</template>

<script setup>
import { ref } from 'vue'
const frameworks = ref([])
</script>

Radio

<template>
  <input type="radio" value="male" v-model="gender" />
  <input type="radio" value="female" v-model="gender" />
  <p>Пол: {{ gender }}</p>
</template>

<script setup>
import { ref } from 'vue'
const gender = ref('male')
</script>

Select

<template>
  <select v-model="selected">
    <option disabled value="">Выберите</option>
    <option value="A">Опция A</option>
    <option value="B">Опция B</option>
    <option value="C">Опция C</option>
  </select>
  <p>Выбрано: {{ selected }}</p>
</template>

<script setup>
import { ref } from 'vue'
const selected = ref('')
</script>

Select (multiple)

<template>
  <select v-model="selected" multiple>
    <option value="A">Опция A</option>
    <option value="B">Опция B</option>
    <option value="C">Опция C</option>
  </select>
  <p>Выбрано: {{ selected }}</p>
</template>

<script setup>
import { ref } from 'vue'
const selected = ref([])
</script>

Модификаторы v-model

.lazy

По умолчанию v-model обновляется на событие input. Модификатор .lazy переключает на change:

<template>
  <!-- Обновится только после потери фокуса -->
  <input v-model.lazy="message" />
</template>

.number

Автоматически преобразует значение в число:

<template>
  <input v-model.number="age" type="number" />
</template>

<script setup>
import { ref } from 'vue'
const age = ref(0)
// age всегда будет числом, а не строкой
</script>

.trim

Автоматически удаляет пробелы в начале и конце:

<template>
  <input v-model.trim="username" />
</template>

<script setup>
import { ref } from 'vue'
const username = ref('')
</script>

Комбинирование модификаторов

<template>
  <input v-model.lazy.trim="message" />
  <input v-model.number.lazy="age" type="number" />
</template>

v-model в пользовательских компонентах

Options API

В Options API v-model работает с prop modelValue и событием update:modelValue:

<!-- ParentComponent.vue -->
<template>
  <CustomInput v-model="searchText" />
</template>

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

const searchText = ref('')
</script>
<!-- CustomInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

Composition API

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

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

Вычисляемое свойство для упрощения

<!-- CustomInput.vue -->
<template>
  <input v-model="value" />
</template>

<script setup>
import { computed } from 'vue'

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

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

Множественный v-model (Vue 3)

В Vue 3 можно использовать несколько v-model на одном компоненте:

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

<script setup>
import { ref } from 'vue'

const firstName = ref('')
const lastName = ref('')
</script>
<!-- UserName.vue -->
<template>
  <input
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
    placeholder="Имя"
  />
  <input
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
    placeholder="Фамилия"
  />
</template>

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

Модификаторы в пользовательских компонентах

Можно создавать собственные модификаторы:

<!-- Parent.vue -->
<template>
  <MyComponent v-model.capitalize="myText" />
</template>
<!-- MyComponent.vue -->
<template>
  <input
    :value="modelValue"
    @input="handleInput"
  />
</template>

<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: {
    default: () => ({})
  }
})

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

function handleInput(event) {
  let value = event.target.value
  
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  
  emit('update:modelValue', value)
}
</script>

v-model vs :value + @input

Когда использовать v-model

<!-- Простой случай -->
<input v-model="username" />

Когда использовать :value + @input

<!-- Нужна дополнительная логика -->
<input
  :value="username"
  @input="handleInput"
/>

<script setup>
function handleInput(event) {
  const value = event.target.value
  
  // Дополнительная валидация
  if (value.length <= 20) {
    username.value = value
  }
}
</script>

Практические примеры

Форма регистрации

<template>
  <form @submit.prevent="handleSubmit">
    <input
      v-model.trim="form.username"
      placeholder="Имя пользователя"
    />
    
    <input
      v-model.trim="form.email"
      type="email"
      placeholder="Email"
    />
    
    <input
      v-model.number="form.age"
      type="number"
      placeholder="Возраст"
    />
    
    <select v-model="form.country">
      <option value="">Выберите страну</option>
      <option value="ru">Россия</option>
      <option value="us">США</option>
    </select>
    
    <label>
      <input type="checkbox" v-model="form.agree" />
      Согласен с условиями
    </label>
    
    <button :disabled="!form.agree">Зарегистрироваться</button>
  </form>
</template>

<script setup>
import { reactive } from 'vue'

const form = reactive({
  username: '',
  email: '',
  age: 0,
  country: '',
  agree: false
})

function handleSubmit() {
  console.log('Form data:', form)
}
</script>

Поиск с debounce

<template>
  <input v-model="searchQuery" placeholder="Поиск..." />
  <p>Ищем: {{ debouncedQuery }}</p>
</template>

<script setup>
import { ref, watch } from 'vue'

const searchQuery = ref('')
const debouncedQuery = ref('')

let timeoutId = null

watch(searchQuery, (newValue) => {
  clearTimeout(timeoutId)
  
  timeoutId = setTimeout(() => {
    debouncedQuery.value = newValue
    // Здесь можно выполнить запрос к API
  }, 500)
})
</script>

Частые ошибки

Изменение props напрямую

<!-- Неправильно -->
<template>
  <input v-model="modelValue" />
</template>

<script setup>
defineProps(['modelValue'])
// Vue предупредит об изменении props
</script>

<!-- Правильно -->
<template>
  <input v-model="value" />
</template>

<script setup>
import { computed } from 'vue'

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

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

Использование v-model на компонентах без правильных props/emits

<!-- Неправильно - компонент не получит данные -->
<CustomComponent v-model="data" />

<!-- Правильно - компонент должен принимать modelValue -->
<script setup>
// В CustomComponent
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

Вывод

v-model:

  • Создаёт двустороннее связывание данных
  • Синтаксический сахар над :value и @input
  • Работает с разными элементами форм
  • Имеет модификаторы: .lazy, .number, .trim
  • В Vue 3 поддерживает множественное использование
  • Можно использовать в пользовательских компонентах

На собеседовании:

Важно уметь:

  • Объяснить, что такое v-model и как он работает
  • Показать, как v-model раскрывается в :value + @input
  • Рассказать о модификаторах
  • Объяснить, как использовать v-model в пользовательских компонентах
  • Привести примеры использования с разными элементами форм