Архитектура Vest изначально ориентирована на независимость от UI-слоя и конкретной платформы. Это означает, что ядро библиотеки не привязано к DOM, компонентным моделям или жизненному циклу приложений. Однако на практике в реальных приложениях требуется тесная интеграция с фреймворками, такими как React, Vue.js, Angular и другими. Для этого используются адаптеры — промежуточные слои, обеспечивающие синхронизацию состояния валидации с моделью данных и жизненным циклом компонентов.
Адаптер в контексте Vest выполняет функцию связывания чистой логики правил с реактивной системой конкретного фреймворка. Само ядро библиотеки оперирует декларативными схемами и результатами выполнения правил, не зная ничего о том, где и как они отображаются.
Основные задачи адаптера:
Таким образом, адаптер выступает прослойкой между чистой логикой и реактивной моделью UI.
В основе интеграции лежит принцип: валидатор существует отдельно от формы. Форма лишь подписывается на изменения состояния.
Типичный поток данных:
Независимо от фреймворка, адаптер можно разложить на несколько логических частей:
Пример упрощённой модели:
function createAdapter(validateFn) {
let state = {
values: {},
errors: {},
};
function setValue(field, value) {
state.values[field] = value;
const result = validateFn(state.values);
state.errors = extractErrors(result);
return state;
}
function getState() {
return state;
}
return {
setValue,
getState,
};
}
Такая структура легко адаптируется под любую реактивную систему.
В экосистеме React ключевую роль играет состояние компонента и хуки.
Адаптер обычно реализуется через useEffect,
useMemo и useState.
Основная задача — обеспечить минимальное количество ререндеров при изменении валидируемых данных.
import { useState, useCallback } from "react";
export function useVestAdapter(validate) {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const setField = useCallback((field, value) => {
const nextValues = { ...values, [field]: value };
setValues(nextValues);
const result = validate(nextValues);
setErrors(extractErrors(result));
}, [values, validate]);
return {
values,
errors,
setField,
};
}
В экосистеме Vue.js основная модель — реактивность через
reactive и watch.
import { reactive, watch } from "vue";
export function useVestAdapter(validate) {
const state = reactive({
values: {},
errors: {},
});
watch(
() => state.values,
(newValues) => {
const result = validate(newValues);
state.errors = extractErrors(result);
},
{ deep: true }
);
function setField(field, value) {
state.values[field] = value;
}
return state;
}
В Angular используется модель реактивных форм и потоков данных через RxJS.
import { FormGroup, FormControl } from "@angular/forms";
export function createVestAdapter(validate) {
const form = new FormGroup({
name: new FormControl(""),
email: new FormControl(""),
});
form.valueChanges.subscribe(values => {
const result = validate(values);
const errors = extractErrors(result);
Object.keys(errors).forEach(field => {
form.get(field)?.setErrors(errors[field]);
});
});
return form;
}
ReactiveFormsModule;setErrors;В серверных средах или при использовании JavaScript без UI часто применяется чистая функция-обёртка.
export function createValidatorAdapter(validate) {
let cache = {};
return function validateField(values) {
const result = validate(values);
cache = extractErrors(result);
return cache;
};
}
Такой подход используется:
При интеграции с адаптерами важным становится выбор стратегии обновления:
Проверка происходит при каждом изменении поля. Подходит для:
Проверка выполняется при потере фокуса или отправке формы.
Группировка обновлений снижает нагрузку:
let queue = [];
function batchUpdate(updateFn) {
queue.push(updateFn);
setTimeout(() => {
queue.forEach(fn => fn());
queue = [];
}, 0);
}
Vest возвращает структурированный результат, который адаптер должен привести к формату фреймворка.
Типичная задача — преобразование вложенных ошибок в плоскую структуру:
function extractErrors(result) {
const errors = {};
result.tests.forEach(test => {
if (test.errors.length) {
errors[test.field] = test.errors;
}
});
return errors;
}
Ключевые аспекты:
Асинхронные проверки (например, запросы к серверу) требуют отдельного слоя управления:
let currentRequest = null;
async function validateAsync(values) {
if (currentRequest) {
currentRequest.cancel?.();
}
currentRequest = createRequest(values);
const result = await currentRequest;
return result;
}
В реальных приложениях адаптер становится критическим элементом производительности.
Основные методы оптимизации:
Частые проблемы:
Правильный адаптер всегда стремится к изоляции: он не должен знать о визуальном представлении данных, а лишь о их структуре и правилах проверки.