你好! Меня зовут Дмитрий, я фронтенд-разработчик в компании fuse8. Сегодня мы рассмотрим, как можно проводить валидацию кастомных компонентов в формах из UI-библиотеки ElementPlus.

Если вы работали с формами в ElementPlus, то наверняка знаете, что библиотека предоставляет простой интерфейс для валидации. Но что делать, если в форме используется кастомный компонент и необходимо применить правило валидации, которое передали в форму? С этим и разберёмся.

Базовый пример валидации из документации

Начнем с простого примера, чтобы освежить понимание общего механизма валидации, который описан в документации ElementPlus.

<template>
 <div class="test">
    <el-form ref="ruleFormRef" 
        :model="ruleForm" 
        :rules="rules" 
        label-position="top" 
        @submit.prevent="submitForm">
          
      <el-form-item label="Activity name" prop="name">
        <el-input v-model="ruleForm.name" clearable />
      </el-form-item>

      <button type="submit">Send</button>
    </el-form>
  </div>
 </template>

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

const ruleFormRef = ref();

//Данные формы
const ruleForm = ref({
  name: '',
});

//Правила валидации
const rules = {
  name: [
    { required: true, message: 'Please input Activity name', trigger: 'change' }
  ],
};



//Применение валидации
const submitForm = async () => {
  if (!ruleFormRef.value) {
    return;
  }
  await ruleFormRef.value.validate((valid, fields) => {
    if (valid) {
      console.log('submit!');
    } else {
      console.log('error submit!', fields);
    }
  });
};
</script>
  1. el-form — компонент формы, который ожидает модель формы (ruleForm) и правила обработки полей (ruless).

  2. el-form-item — дочерний компонент, который использует prop для  связи с моделью формы.

  3. el-input — стандартный компонент ввода из ElementUI.

При нажатии на кнопку Send вызывает submitForm(), где вызывается валидация из компонента el-form, для каждого правила описанного в rules.

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

Если попытаться отправить пустую форму из примера выше, библиотека подсветит поле и выведет сообщение об ошибке.

Применение валидации к пустому полю
Применение валидации к пустому полю

Если начнем вводить данные, то подсветка пропадёт.

- Круто?

- Круто!

Но что будет, если у нас есть какой-то кастомный компонент или простой инпут с двухсторонним связыванием?

...
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-position="top" @submit.prevent="submitForm">
      <el-form-item label="Activity name" prop="name">
        <input type="text" v-model="ruleForm.name"/> // стандартный html тег input
      </el-form-item>
      <button type="submit">Send</button>
    </el-form>
...

И тут, если мы  попытаемся отправить пустую форму, то валидация сработает – вызовется функция submitForm(), и пустое поле подсветится. Однако, если мы начнем вводить текст, то подсветка никуда не денется. И да, простите, что не стал заморачиваться со стилями!

Правило валидации не применилось к полю
Правило валидации не применилось к полю

В общем, именно с такой проблемой я столкнулся - вроде на уровне формы валидация есть, а вот на уровне компонента нет. В документации описания того, как это можно победить я не нашел (может плохо искал).

Поиск решения

Вооружившись тяжелым, пристальным взглядом и банкой несквика, пошел в исходники. Если коротко, то внутри компонента el-form генерируется контекст из всего того, что мы передали, и, используя механизм Provide / inject, пробрасывается потомкам. То же самое происходит и в elFormItem. Ключами в provide выступают символы:

Слава Линуксу, создатели ElementPlus заботливо дали возможность импортировать эти символы напрямую из elements-plus.

В сгенерированном контексте нас интересует функция validate(), которая, как нетрудно догадаться, вызывает правило валидации для конкретного поля (prop=”name”).

Теперь дело осталось за малым, делаем watch за изменениями элементов, проводим inject контекста и вызываем валидацию нашего поля при изменении.

<-- CustomInput.vue -->
<template>
  <input v-model="model" />
</template>
<script setup>
import { inject, watch } from 'vue';
import { formItemContextKey } from 'element-plus';
const model = defineModel(); //модный сахарок из Vue 3.4
const elFormItem = inject(formItemContextKey, undefined); //берем контекст  

//Следим за изменением данных компонента и применяем валидацию
watch(model, () => { 
  elFormItem?.validate?.('change');
});
</script>

Теперь наш компонент реагирует на изменение данных и применяет валидацию.

Таким образом можно интегрировать ваши компоненты в механизм валидации, который предоставляет ElementPlus. Возможно, есть более изящный вариант, но я его не нашел. Предлагаю вам поделиться в комментариях. 

PS:  Все, конечно же, поняли отсылку в начале - типа поздоровался по-китайски и библиотека китайская. Шучу. Был в Китае, выучил 2 слова, не мог не блеснуть =)

Комментарии (0)