Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як зробити власний компонент поля форми у Vue через defineModel і не загубити атрибути й помилки?

У формах повторюється той самий набір: мітка, поле, підказка, помилка, атрибути доступності. Його виносять у компонент поля.

defineModel (Vue 3.4+) - найкоротший спосіб підтримати v-model на власному компоненті:

<!-- TextField.vue -->
<script setup lang="ts">
import { useId } from 'vue'

defineOptions({ inheritAttrs: false })

const model = defineModel<string>({ required: true })
const props = defineProps<{ label: string; error?: string; hint?: string }>()
const id = useId()
</script>

<template>
  <div class="field">
    <label :for="id">{{ props.label }}</label>
    <input
      :id="id"
      v-model="model"
      v-bind="$attrs"
      :aria-invalid="props.error ? 'true' : undefined"
      :aria-describedby="props.error ? `${id}-error` : undefined"
    />
    <p v-if="props.hint && !props.error">{{ props.hint }}</p>
    <p v-if="props.error" :id="`${id}-error`" class="error">{{ props.error }}</p>
  </div>
</template>
<TextField v-model="form.email" label="Email" type="email" autocomplete="email" :error="errors.email?.[0]" />

Що тут важливо:

  • defineModel створює ref, синхронізований з батьком: читання - значення modelValue, запис - подія update:modelValue. Без нього довелося б оголошувати проп і подію вручну;
  • inheritAttrs: false + v-bind="$attrs" - атрибути, передані компоненту (type, autocomplete, placeholder, required), потрапляють на <input>, а не на обгортку div. Інакше type="email" опиниться на div і не працюватиме;
  • модифікатори теж підтримуються: const [model, modifiers] = defineModel({ set(v) { return modifiers.trim ? v.trim() : v } });
  • кілька моделей на одному компоненті: defineModel('from') і defineModel('to') для діапазону дат - v-model:from і v-model:to.

Типові помилки:

  • мутувати проп напряму (props.modelValue = ...) - Vue попередить, а батько не дізнається про зміну;
  • локальна копія значення без синхронізації - поле перестає реагувати на скидання форми батьком;
  • об'єкт у defineModel і зміна його властивостей (model.value.city = ...) мутує об'єкт батька в обхід події - для об'єктів присвоюйте нове значення (model.value = { ...model.value, city }).

Межа абстракції: компонент поля відповідає за розмітку й доступність, а не за валідацію. Правила й стан форми лишаються у формі чи бібліотеці валідації - тоді той самий TextField працює з будь-яким підходом.

Докладніше в документації: Vue: v-model на компонентах

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання