← Назад к вопросам

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

2.0 Middle🔥 131 комментариев
#Soft Skills и рабочие процессы

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

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мой подход к кастомной валидации в веб-разработке

Да, я регулярно пишу кастомную валидацию в своих проектах, поскольку встроенных механизмов HTML5 и стандартных валидаторов часто недостаточно для сложных бизнес-правил. Кастомная валидация позволяет создавать более точные, пользовательские и контекстно-зависимые проверки данных.

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

Я использую кастомные решения в следующих случаях:

  1. Сложные бизнес-правила, которые невозможно выразить через стандартные атрибуты вроде pattern или required
  2. Зависимые поля, когда валидность одного поля зависит от значения другого
  3. Асинхронные проверки, например, проверка уникальности email или username через API
  4. Кастомные сообщения об ошибках с локализацией и специфичной для приложения логикой
  5. Валидация динамических форм, где структура формы меняется в зависимости от действий пользователя

Примеры реализации

1. Синхронная валидация с использованием Constraint Validation API

class CustomValidator {
  static validatePassword(password) {
    const errors = [];
    
    if (password.length < 8) {
      errors.push('Пароль должен содержать минимум 8 символов');
    }
    
    if (!/[A-Z]/.test(password)) {
      errors.push('Пароль должен содержать хотя бы одну заглавную букву');
    }
    
    if (!/\d/.test(password)) {
      errors.push('Пароль должен содержать хотя бы одну цифру');
    }
    
    if (!/[!@#$%^&*]/.test(password)) {
      errors.push('Пароль должен содержать хотя бы один специальный символ');
    }
    
    return {
      isValid: errors.length === 0,
      errors
    };
  }

  static validatePhone(phone) {
    // Российские номера в различных форматах
    const phoneRegex = /^(\+7|8)[\s\-]?\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}$/;
    return phoneRegex.test(phone);
  }
}

// Использование в React компоненте
const PasswordField = () => {
  const [errors, setErrors] = useState([]);
  
  const handlePasswordChange = (e) => {
    const validation = CustomValidator.validatePassword(e.target.value);
    setErrors(validation.errors);
    
    // Установка кастомной валидности для поля
    e.target.setCustomValidity(
      validation.isValid ? '' : validation.errors.join('. ')
    );
  };
  
  return (
    <div>
      <input 
        type="password" 
        onChange={handlePasswordChange}
        required
      />
      {errors.map((error, index) => (
        <div key={index} className="error">{error}</div>
      ))}
    </div>
  );
};

2. Асинхронная валидация с debounce

import { debounce } from 'lodash';

const AsyncValidator = {
  checkEmailAvailability: debounce(async (email) => {
    if (!email || email.length < 3) {
      return { available: false, message: 'Email слишком короткий' };
    }
    
    try {
      const response = await fetch(`/api/check-email?email=${encodeURIComponent(email)}`);
      const data = await response.json();
      return data;
    } catch (error) {
      console.error('Ошибка проверки email:', error);
      return { available: false, message: 'Ошибка сервера' };
    }
  }, 500),

  validateAsyncForm: async (formData) => {
    const validations = await Promise.all([
      this.checkEmailAvailability(formData.email),
      // другие асинхронные проверки
    ]);
    
    return validations.every(v => v.available);
  }
};

3. Валидация с использованием библиотек и собственных решений

Я часто комбинирую библиотечные решения с кастомной логикой:

// Пример с Yup и кастомными правилами
import * as yup from 'yup';

const customValidationSchema = yup.object().shape({
  username: yup
    .string()
    .required('Имя пользователя обязательно')
    .test(
      'no-spaces',
      'Имя пользователя не должно содержать пробелы',
      value => !/\s/.test(value)
    )
    .test(
      'custom-rule',
      'Имя пользователя должно содержать хотя бы одну цифру',
      value => /\d/.test(value)
    ),
  
  birthDate: yup
    .date()
    .required()
    .test(
      'age-restriction',
      'Пользователь должен быть старше 18 лет',
      function(value) {
        const today = new Date();
        const birthDate = new Date(value);
        const age = today.getFullYear() - birthDate.getFullYear();
        const monthDiff = today.getMonth() - birthDate.getMonth();
        
        return age > 18 || (age === 18 && monthDiff >= 0);
      }
    ),
  
  confirmPassword: yup
    .string()
    .oneOf([yup.ref('password'), null], 'Пароли должны совпадать')
    .required('Подтверждение пароля обязательно')
});

// Кастомная валидация для сложных условий
const validateBusinessRules = (values) => {
  const errors = {};
  
  // Пример: проверка зависимости полей
  if (values.subscriptionType === 'premium' && !values.creditCard) {
    errors.creditCard = 'Для премиум подписки требуется кредитная карта';
  }
  
  // Пример: кросс-полевая валидация
  if (values.startDate && values.endDate) {
    const start = new Date(values.startDate);
    const end = new Date(values.endDate);
    
    if (end <= start) {
      errors.endDate = 'Дата окончания должна быть позже даты начала';
    }
    
    const diffTime = Math.abs(end - start);
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    
    if (diffDays > 365) {
      errors.endDate = 'Период не может превышать 1 год';
    }
  }
  
  return errors;
};

Лучшие практики, которые я соблюдаю

  1. Многоуровневая валидация:

    • Клиентская валидация для мгновенного feedback
    • Серверная валидация для безопасности
    • Валидация на уровне бизнес-логики
  2. Юзабилити аспекты:

    • Валидация на лету (onChange) для сложных полей
    • Валидация при потере фокуса (onBlur) для простых полей
    • Отложенная валидация с debounce для асинхронных проверок
    • Показ ошибок в контексте поля и сводной форме
  3. Производительность:

    • Мемоизация функций валидации
    • Отмена pending асинхронных запросов
    • Ленивая валидация для невидимых полей
  4. Доступность (a11y):

    • Связь ошибок с полями через aria-describedby
    • Правильные роли и алерты для скринридеров
    • Фокус на первую ошибку после сабмита

Архитектурные подходы

Я предпочитаю создавать реиспользуемые валидаторы, которые можно тестировать изолированно. Часто выношу валидационную логику в отдельные сервисы или хук-функции:

// Кастомный хук для валидации в React
const useValidation = (schema, initialValues) => {
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  
  const validateField = useCallback(async (name, value) => {
    try {
      await schema.validateAt(name, { [name]: value });
      setErrors(prev => ({ ...prev, [name]: '' }));
      return true;
    } catch (error) {
      setErrors(prev => ({ ...prev, [name]: error.message }));
      return false;
    }
  }, [schema]);
  
  const validateForm = useCallback(async (values) => {
    try {
      await schema.validate(values, { abortEarly: false });
      setErrors({});
      return { isValid: true, errors: {} };
    } catch (validationErrors) {
      const formattedErrors = {};
      validationErrors.inner.forEach(error => {
        formattedErrors[error.path] = error.message;
      });
      setErrors(formattedErrors);
      return { isValid: false, errors: formattedErrors };
    }
  }, [schema]);
  
  return {
    errors,
    touched,
    setTouched,
    validateField,
    validateForm,
    hasErrors: Object.values(errors).some(error => error)
  };
};

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

Пишешь ли кастомную валидацию | PrepBro