Skip to content

v-model з компонентами

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

Спочатку давайте переглянемо, як 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, створити спеціальну подію 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>
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['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>
vue
<!-- CustomInput.vue -->
<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>

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

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

За промовчанням v-model для компонента використовує modelValue як реквізит і update:modelValue як подію. Ми можемо змінити ці імена, передавши аргумент v-model:

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

У цьому випадку дочірній компонент має очікувати реквізит title і випромінювати подію update:title для оновлення батьківського значення:

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

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

Спробуйте в пісочниці

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="firstName"
  v-model:last-name="lastName"
/>
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, будуть надані компоненту через реквізит modelModifiers. У наведеному нижче прикладі ми створили компонент, який містить реквізит modelModifiers, яка за промовчанням має порожній об’єкт:

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

defineEmits(['update:modelValue'])

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

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
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 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>

Спробуйте в пісочниці

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
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }
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 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 з компонентами has loaded