Кастомные компоненты форм

Vue.js предоставляет мощные возможности для создания интерактивных пользовательских интерфейсов, и формы играют здесь ключевую роль. Встроенные элементы <input>, <select>, <textarea> покрывают базовые сценарии, но при разработке сложных приложений часто требуется создавать кастомные компоненты форм, обеспечивающие переиспользуемость, валидируемость и интеграцию с реактивной системой Vue.


Создание базового кастомного компонента

Базовая структура кастомного компонента формы во Vue выглядит следующим образом:

<template>
  <div class="form-field">
    <label :for="id">{{ label }}</label>
    <input
      :id="id"
      :type="type"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    />
  </div>
</template>

<script>
export default {
  name: 'CustomInput',
  props: {
    modelValue: String,
    label: String,
    type: {
      type: String,
      default: 'text'
    },
    id: {
      type: String,
      required: true
    }
  }
}
</script>

Ключевые моменты:

  • Использование v-model через modelValue и событие update:modelValue обеспечивает двустороннюю привязку данных.
  • Компонент полностью управляется через свойства props, что повышает его переиспользуемость.
  • События @input позволяют родительскому компоненту контролировать значение поля.

Работа с различными типами ввода

Кастомный компонент может быть расширен для поддержки нескольких типов данных:

<template>
  <div class="form-field">
    <label :for="id">{{ label }}</label>
    <component
      :is="inputType"
      :id="id"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    ></component>
  </div>
</template>

<script>
export default {
  props: {
    modelValue: [String, Number, Boolean],
    label: String,
    type: {
      type: String,
      default: 'text'
    },
    id: {
      type: String,
      required: true
    }
  },
  computed: {
    inputType() {
      if (this.type === 'textarea') return 'textarea';
      if (this.type === 'checkbox') return 'input';
      return 'input';
    }
  }
}
</script>
  • Для checkbox и radio требуется дополнительная обработка checked вместо value.
  • Поля типа number могут использовать v-model.number или ручное преобразование через parseFloat/parseInt.

Валидация внутри кастомного компонента

Валидация — важная часть компонентов форм. Vue позволяет интегрировать проверку данных прямо в компонент:

<template>
  <div class="form-field">
    <label :for="id">{{ label }}</label>
    <input
      :id="id"
      :type="type"
      :value="modelValue"
      @input="handleInput"
    />
    <span v-if="errorMessage" class="error">{{ errorMessage }}</span>
  </div>
</template>

<script>
export default {
  props: {
    modelValue: String,
    label: String,
    id: String,
    rules: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      errorMessage: ''
    };
  },
  methods: {
    handleInput(event) {
      const value = event.target.value;
      this.$emit('update:modelValue', value);
      this.validate(value);
    },
    validate(value) {
      this.errorMessage = '';
      for (const rule of this.rules) {
        const result = rule(value);
        if (result !== true) {
          this.errorMessage = result;
          break;
        }
      }
    }
  }
}
</script>
  • rules — массив функций, возвращающих true при успешной проверке или текст ошибки.
  • Валидация выполняется при каждом вводе, что обеспечивает интерактивную обратную связь пользователю.

Интеграция кастомных компонентов с v-model

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

<CustomInput v-model="formData.username" label="Имя пользователя" id="username" />
<CustomInput v-model="formData.password" type="password" label="Пароль" id="password" />
  • В formData хранится объект с ключами, соответствующими каждому полю.
  • Такой подход упрощает работу с формами, включая сериализацию данных для отправки на сервер.

Композиция сложных форм

Для крупных форм часто создают обертки, которые объединяют несколько кастомных компонентов и управляют их состоянием:

<template>
  <form @submit.prevent="handleSubmit">
    <CustomInput v-model="form.username" label="Логин" id="login" :rules="[required]" />
    <CustomInput v-model="form.email" label="Email" id="email" :rules="[required, email]" />
    <button type="submit">Отправить</button>
  </form>
</template>

<script>
import CustomInput from './CustomInput.vue';

export default {
  components: { CustomInput },
  data() {
    return {
      form: {
        username: '',
        email: ''
      }
    };
  },
  methods: {
    handleSubmit() {
      console.log('Данные формы:', this.form);
    },
    required(value) {
      return value ? true : 'Поле обязательно';
    },
    email(value) {
      const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return regex.test(value) ? true : 'Неверный формат email';
    }
  }
}
</script>
  • Обертка позволяет централизовать валидацию и обработку данных.
  • Каждое поле может иметь собственный набор правил, при этом весь процесс остается реактивным.

Динамическое управление формами

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

<template>
  <div v-for="(field, index) in fields" :key="index">
    <CustomInput v-model="field.value" :label="field.label" :id="'field-' + index" />
    <button @click="removeField(index)">Удалить</button>
  </div>
  <button @click="addField">Добавить поле</button>
</template>

<script>
import CustomInput from './CustomInput.vue';

export default {
  components: { CustomInput },
  data() {
    return {
      fields: [
        { label: 'Поле 1', value: '' }
      ]
    };
  },
  methods: {
    addField() {
      this.fields.push({ label: `Поле ${this.fields.length + 1}`, value: '' });
    },
    removeField(index) {
      this.fields.splice(index, 1);
    }
  }
}
</script>
  • Использование реактивного массива fields позволяет создавать динамические формы с любым количеством полей.
  • Компоненты автоматически отслеживают изменения благодаря реактивности Vue.

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

Кастомные компоненты форм легко интегрируются с Vuex или Composition API для управления глобальным состоянием:

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

const formState = reactive({
  name: '',
  email: ''
});
</script>

<template>
  <CustomInput v-model="formState.name" label="Имя" id="name" />
  <CustomInput v-model="formState.email" label="Email" id="email" />
  <pre>{{ formState }}</pre>
</template>
  • Реактивный объект formState обновляется автоматически при изменении значений в компонентах.
  • Можно легко отслеживать изменения и синхронизировать данные с сервером или локальным хранилищем.

Кастомные компоненты форм во Vue.js обеспечивают гибкость, удобство переиспользования и полное соответствие реактивной модели. При правильной организации они становятся фундаментом для сложных и масштабируемых приложений.