Относительно недавно в TC39 предложение с реализацией декораторов в EcmaScript вышло на 3-ю предфинальную стадию. Чуть позже MicroSoft выпустил 5-ю мажорную версию TypeScript, в которой новая реализация декораторов начала работать из коробки без каких-либо экспериментальных флагов. Babel тоже подсуетился, и в своей документации начал рекомендовать использовать новую реализацию декораторов. А это означает лишь то, что декораторы наконец-то начали полноценно входить в жизнь JavaScript разработчиков.

И на волне этого хайпа я решил рассказать, как, используя декораторы, можно улучшить ваш Developer Experience при разработке форм.

Важное упоминание. В статье я буду писать о подходе, использующем библиотеку MobX. Так что если вы в своих проектах её не используете, статья может быть не так полезна. Но вы можете рассматривать её, как возможный источник вдохновения по тому, как можно разрабатывать формы.

Предыстория

На моем прошлом рабочем проекте мне приходилось разрабатывать много сложных форм. Нередко они состояли из нескольких десятков полей.

Каждое поле нужно было валидировать. Разумеется, простые правила валидации, такие как проверка на заполненность поля или проверка на валидность e-mail адреса, часто повторялись. Но порой эти правила становились невероятно сложными. Например, в зависимости от того, что пользователь выберет в одном поле, правила валидации для другого поля могли меняться. А ещё в некоторых случаях нужно было выключать валидацию одного поля, при определенных значениях другого. Ну и, разумеется, на каждом поле могло быть несколько правил валидации, каждое из которых должно было выдавать собственное сообщение об ошибке.

Но валидация это лишь часть необходимого функционала. Еще формы нужно было отслеживать по изменениям. Условно говоря, блокировать кнопку отправки, пока пользователь не внесет изменения. Но, повторюсь, полей на форме десятки, так что вручную писать if’ы для каждого поля было не самым лучшим решением.

Ситуация усугублялась тем, что некоторые поля могли представлять массивы и set’ы данных. И если пользователь удалил несколько значений из такого поля, а затем ручками ввел те же самые значения, форма должна была понимать, что она вернулась в исходное состояние.

В дополнение к этому, формы должны были быть способны сбрасывать текущее состояние формы до исходного. Ну и разумеется, они должны были уметь общаться с сервером.

Я рассматривал к использованию различные библиотеки, такие как React Hook Form или Formik, но эти варианты мне не подходили. На масштабе тех требований код даже с этими библиотеками получался слишком громоздким и сложно поддерживаемым. А потому я начал думать над собственным решением.

MobX Form Schema

Разделение представления и логики - вот с чего я начал. Надо было продумать способ как-то описывать логику формы в отдельной функции или объекте и по возможности максимально минимизировать необходимость написания повторяемого кода.

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

В самом простом представлении такой объект является обычным MobX стором. Например, в сниппете кода ниже представлен простейший пример схемы формы из двух полей: "Имя" и "Фамилия". Пока что без какой-либо логики.

import { makeObservable, observable } from 'mobx';

export class BasicFormStore {
  name = '';

  surname = '';

  constructor() {
    makeObservable(this, {
      name: observable,
      surname: observable,
    });
  }
}

Валидация формы

Что есть валидация поля? Это одно или несколько правил проверки значения поля. В нашем случае "поле" - это свойство класса. А значит несколько правил валидации можно назначить соответствующему свойству при помощи декоратора - @validate.

import { FormSchema, validate } from '@yoskutik/mobx-form-schema';
import { makeObservable, observable } from 'mobx';
import { email, required } from 'path/to/validators';

export class LoginSchema extends FormSchema {
  @validate(required(), email())
  email = '';

  constructor() {
    super();
    makeObservable(this, {
      email: observable,
    });
  }
}

const schema = LoginSchema.create();
console.log(schema.isValid, schema.errors);
// false, { email: 'The field is required' }

schema.email = 'invalid.email';
console.log(schema.isValid, schema.errors);
// false, { email: 'Invalid email format' }

schema.email = 'valid@email.com';
console.log(schema.isValid, schema.errors);
// true, {}

Вы просто передаете в декоратор несколько функций-валидаторов, а схема сама валидирует значение поля. Если валидаторов несколько, схема применяет их поочередно. И только если все правила успешно пройдены, схема признает поле валидным.

И снова мы видим разделение нашего кода. Описание логики формы держится отдельно от декларации валидаторов. Сделано это, разумеется, специально. Таким образом формируется подход к написанию атомарных функций валидации. И благодаря этому общие правила могут быть с легкостью переиспользованы.

Как выглядит функция-валидатор?

Функция-валидатор для схемы - это просто функция, которая возвращает либо строковое, либо булевое значение.

export const required = () => (value?: string) => {
  if (value?.trim()) return false;
  return 'This field is required';
};

export const email = () => (value: string) => {
  if (/\S+@\S+\.\S+/.test(value)) return false;
  return 'Invalid email format';
};

export const minLength = (min: number) => (value: string) => {
  if (value.length >= min) return false;
  return `Should be at least ${min} characters.`;
};

Если функция возвращает false, валидация считается пройденной. А если строку или true - нет. Причем строка, передаваемая в валидаторе становится сообщением ошибки.

В качестве 1-го параметра на вход функция получает текущее значение свойства. А на случай сложной валидации, каждая функция-валидатор принимает 2-м параметром схему со всеми полями формы.

Поле для подтверждения пароля должно иметь точно такое же значение, что поле пароля. Поле ввода даты "С" должно содержать дату, которая была до даты в поле "По". Как раз в таких случаях при валидации вам может понадобиться схема целиком.

В примере ниже представлена форма для регистрации с примером валидации поля подтверждения пароля.

import { FormSchema, validate } from '@yoskutik/mobx-form-schema';
import { makeObservable, observable } from 'mobx';
import { email, minLength, required } from 'path/to/validators';

const confirmPassword = () => (
  // Используем второй параметр для того, чтобы проверить, что текущее
  //  значение “confirmPassword” совпадает с “password”.
  (confirmPasswordValue: string, schema: SignUpSchema) => {
    if (confirmPasswordValue === schema.password) return false;
    return 'Passwords mismatched';
  }
);

export class SignUpSchema extends FormSchema {
  // Поле ввода e-mail адреса
  @validate(required(), email())
  email = '';

  // Поле ввода пароля
  @validate(required(), minLength(8))
  password = '';

  // Поле ввода подтверждения пароля
  @validate(required(), confirmPassword())
  confirmPassword = '';

  constructor() {
    super();
    makeObservable(this, {
      email: observable,
      password: observable,
      confirmPassword: observable,
    });
  }
}

Условная валидация

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

Т.к. декоратор @validate уже используется для декларации обычной валидации, его мы использовать не можем. Но мы можем создать его модификатор - @validate.if. Такой модификатор будет работать почти также, как оригинал, за тем лишь исключением, что в него помимо массива валидаторов нужно будет передать функцию-предикат, которая будет говорить, нужна ли в данный момент валидация. Если предикат будет говорить, что валидация не нужна, свойство будет признаваться валидным.

В примере ниже представлена схема из трех полей:

  • Необязательное поле для ввода адреса почты.

  • Чекбокс, отмечая который пользователь говорит, что у него есть питомец.

  • И поле для ввода имени питомца. Если чекбокс активен, необходимо произвести валидацию на заполненность поля.

import { FormSchema, validate } from '@yoskutik/mobx-form-schema';
import { makeObservable, observable, runInAction } from 'mobx';
import { email, required } from 'path/to/validators';

const shouldValidatePetName = (_name: string, schema: ConditionalSchema) => (
  schema.doesHavePet
);

export class ConditionalSchema extends FormSchema {
  // or it can be @validate.if(email => !!email, [email()])
  @validate.if(Boolean, [email()])
  email = '';

  doesHavePet = false;

  @validate.if(shouldValidatePetName, [required()])
  petName = '';

  constructor() {
    super();
    makeObservable(this, {
      email: observable,
      doesHavePet: observable,
      petName: observable,
    });
  }
}

const schema = ConditionalSchema.create();
console.log(schema.isValid, schema.errors); // true, {}

runInAction(() => schema.doesHavePet = true);
console.log(schema.isValid, schema.errors);
// false, { petName: 'The value is required.' }

runInAction(() => schema.email = 'invalid.email');
console.log(schema.isValid, schema.errors);
// false, {
//   petName: 'The value is required.',
//   email: 'Invalid email format.',
// }

Это довольно простой пример валидации. Вы можете взглянуть на пример более сложной валидации, в т.ч. условной и т.ч. с использованием схемы полностью на сайте документации.

В какой момент происходит валидация?

По умолчанию, схема запускает расчет валидации в функции autorun из библиотеки MobX. Благодаря этому валидация свойства пересчитывается автоматически при его изменении. Но также благодаря этому же, если в валидации участвовали другие свойства схемы, при их изменении валидация будет также пересчитана.

Такое же правило работает для функции условия валидации. Если было изменено отслеживаемое свойство или то свойство, которое участвует в условии, функция-предикат вызовется снова.

Переживать за лишние пересчеты не стоит. Благодаря MobX и оптимизациям MobX Form Schema лишних пересчетов не происходит. Однако, отключить автоматическую валидацию и начать валидировать данные вручную возможно. Можете посмотреть на примеры ручной валидации по ссылке.

Краткое резюме по валидации

Эти плюсы могут показаться субъективными, но такой подход в оформлении кода мне очень понравился. Он хорошо читаем, и хорошо поддерживаем. Он достаточно гибок, и даже в сложных кейсах не заставляет писать мудреную логику.

Минусом может показаться необходимость писать валидаторы с нуля, даже самые базовые, в то время как другие библиотеки поставляют их из коробки. Но против этого пункта у меня есть возражения:

  • Даже базовые правила могут отличаться на разных проектах. Например, валидация номера телефона или почты в разных странах.

  • В приложении может быть несколько языков. И даже в рамках одного языка могут возникнуть ситуации, когда одно и то же правило в разных полях должно выдавать разные сообщения об ошибках.

Оба эти пункта ведут к необходимости предоставления функционала по переопределению или конфигурации базовых валидаторов. Но как вы видели сами, базовые валидаторы могут состоять из 3 строк кода. И по мне так лучше написать 3 строки кода с нуля, чем написать их для конфигурации готового функционала.

Что ещё здорово - MobX Form Schema работает с декораторами и новой, и старой реализации. Но в новой есть хорошая поддержка типизации. Поэтому я не могу навесить валидатор для числа на строковое свойство.

const rule = () => (value: number) => {
  if (value > 0) return false;
  return 'The value must be greater than 0';
};

export class SignUpSchema extends FormSchema {
  // a typing error here, since `rule` must work with number properties
  @validate(rule())
  email = '';
}

Наблюдение за изменениями формы

Теперь от валидации перейдем к отслеживанию изменений формы. Понять, что форма была изменена несложно. Во-первых, нужно сохранить изначальное состояние формы. Во-вторых, в нужный момент достаточно использовать примерно такой кусочек кода:

const isChanged = currentValue1 !== initialValue1
  || currentValue2 !== initialValue2
  || ...;

Это действенный и простой способ, но он подходит только для простых форм. Чем больше в форме будет полей, тем длиннее будет это условие, и тем сложнее будет поддержка такого кода.

Но это не единственная проблема. Помимо простых текстовых полей, в форме могут быть более комплексные поля. Например, на сайте Хабр.Карьера при заполнении информации о вашей специализации есть поле “Профессиональные навыки”, значение которого по факту должно являться либо массивом, либо множеством. И простое ссылочное сравнение тут уже не поможет, чтобы понять, изменилось ли состояние формы.

Есть другой подход - глубокое сравнение.

import isEqual from 'lodash/isEqual';
const isChanged = isEqual(currentState, initialState);

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

MobX позволяет обойти эти обе проблемы. В схеме формы при изменении определенного поля происходит только то сравнение, которое проверяет было ли изменено конкретно это поле.

И чтобы активировать наблюдение за формой, достаточно использовать декоратор - @watch.

export class UserSchema extends FormSchema {
  @watch name = 'Initial name';

  @watch surname = 'Initial surname';
}

const schema = UserSchema.create();

console.log(schema.isChanged) // false

schema.name = 'New Name';
console.log(
  schema.isChanged,           // true
  schema.getInitial('name'),  // 'Initial name'
);

schema.name = 'Initial name';
console.log(schema.isChanged) // false

В такой форме флаг isChanged будет равен false всегда если имя будет равно "Initial name" а фамилия "Initial surname". Даже если свойство поменяет значение на другое, а затем вернется в исходное состояние.

Декоратор @watch, используя ссылочное сравнение, сообщает схеме, изменилось ли значение свойства от изначального состояния.

Вы могли заметить, что я не вызывал функцию makeObservable в примере выше. Это все потому, что @watch по умолчанию навешивает observable.ref на свойства. Делается из логических соображений - если вам нужно лишь ссылочное сравнение, глубокое наблюдение через observable вам навряд ли понадобится. Однако, самостоятельно добавить его вы можете без каких-либо проблем.

Что еще важно, сама по себе схема не запоминает свое изначальное состояние. Но если вы применяете watch, схема запоминает изначальное состояние только для отмеченных полей. Таким образом лишнего оверхеда по потребляемой памяти нет.

Наблюдение за сложными объектами

Из-за ссылочного сравнения, @watch по большой части необходим для наблюдения за примитивными значениями. С объектами все сложнее - например, при проверке того, был ли массив изменен, нам нужно проверить количество элементов в изначальном и текущем состояниях, а также проверить, что элементы совпадают на каждой из позиций.

Но нам на помощь снова приходят модификаторы декоратора. С помощью них можно создать такие модификаторы, в которых вместо ссылочного сравнения будет какой-то другой тип сравнения. Например, если вы хотите использовать массив значений - используете @watch.array, а если множество - @watch.set. По умолчанию эти декораторы навесят на свойства схемы observable.shallow.

import { FormSchema, watch } from '@yoskutik/mobx-form-schema';
import { runInAction } from 'mobx';

export class ArraySchema extends FormSchema {
  @watch.array skillsArray = ['HTML', 'CSS', 'JavaScript'];

  @watch.set skillsSet = new Set(['HTML', 'CSS', 'JavaScript']);
}

const schema = ArraySchema.create();

runInAction(() => schema.skillsArray = ['HTML']);
console.log(schema.isChanged, schema.changedProperties);
// true, Set(['skillsArray'])

runInAction(() => schema.skillsArray.push('CSS', 'JavaScript'));
console.log(schema.isChanged, schema.changedProperties);
// false, Set()

runInAction(() => schema.skillsSet.delete('CSS'));
console.log(schema.isChanged, schema.changedProperties);
// true, Set(['skillsSet'])

runInAction(() => schema.skillsSet.add('CSS'));
console.log(schema.isChanged, schema.changedProperties);
// false, Set()

А благодаря новым декораторам, мы получаем хорошую типизацию. @watch.array нельзя использовать с не массивами, а @watch.set с не множествами.

Наблюдение за вложенными схемами

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

import { FormSchema, watch } from '@yoskutik/mobx-form-schema';
import { runInAction } from 'mobx';

export class ContactsSchema extends FormSchema {
  @watch tel = 'default tel value';

  @watch email = 'default email value';
}

export class InfoSchema extends FormSchema {
  @watch name = '';

  @watch surname = '';

  @watch.schema contacts = ContactsSchema.create();
}

const schema = InfoSchema.create();

runInAction(() => schema.contacts.tel = 'new value');
console.log(schema.isChanged, schema.changedProperties);
// true, Set(['contacts'])

runInAction(() => schema.contacts.tel = 'default tel value');
console.log(schema.isChanged, schema.changedProperties);
// false, Set()

runInAction(() => schema.contacts = ContactsSchema.create());
console.log(schema.isChanged, schema.changedProperties);
// false, Set()

И такая выделенная отдельная схема, конечно же, может в дальнейшем использоваться и без родительской схемы, если такая ситуация потребуется.

В дополнение к этому, вы можете использовать модификатор @watch.schemasArray на случай, если вам надо использовать массив вложенных схем. Таким массивом, например, может быть массив блоков с информацией об опыте работы в форме для резюме.

На случай, если вы имеете какую-то необычную структуру данных, для сравнения которой нужна особенная функция, вы можете самостоятельно создать модификацию декоратора @watch - для этого вы можете использовать метод watch.create. Но чтобы не раздувать слишком сильно статью, я просто скромно оставлю ссылку на документацию, если вам интересно посмотреть на примеры использования watch.schemasArray и watch.create.

Восстановление формы в исходное состояние

В некоторых случаях может быть полезна функция восстановления формы. Особенно это полезно в формах для редактирования, которые могут быть открыты с предзаполненными данными с сервера. И т.к. мы уже храним изначальное состояние формы, нам не составляет никакого труда восстановить форму в её исходное состояние.

import { FormSchema, watch } from '@yoskutik/mobx-form-schema';
import { runInAction } from 'mobx';

export class BasicSchema extends FormSchema {
  @watch name = 'Joe';

  @watch surname = 'Dough';
}

const schema = BasicSchema.create();

runInAction(() => {
  schema.name = 'new name';
  schema.surname = 'new surname';
});

console.log(schema.name, schema.surname); // 'new name', 'new surname'
schema.reset();
console.log(schema.name, schema.surname); // 'Joe', 'Dough'

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

Краткое резюме по наблюдению за изменениями

Кратко упомяну, что как и с валидацией, отслеживание изменений происходит автоматически. Но, конечно же, возможность ручной проверки изменений тоже доступна.

А так, всего пара декораторов и методов - и полностью  автоматизированное наблюдение за изменениями в форме готово. Какой бы сложной бы не была схема, и сколько бы вложенных схем в ней бы не было, вы всегда можете понимать, изменена ли ваша форма. И вы всегда можете восстановить её в исходное состояние.

Вы можете даже описать схему формы настроек IDE. Обычно в таких формах присутствует множество вкладок, внутри которых есть вложенные вкладки. И вы без какого-либо труда можете отслеживать и сбрасывать формы как на глобальном уровне, так и на первом уровне вложенности, так и на последнем. 

При этом эти наблюдения являются дешёвой операцией. При изменении поля происходит проверка только этого поля.

Ну и, разумеется, то, что @watch и его модификации способны сами по себе делать свойства observable, позволяет ещё больше сократить ваш код.

Общение формы с сервером

Иногда данные, приходящие с сервера, требуют какой-либо предобработки перед их использованием. Например, сервер не может прислать сущность Set или Date, но вам может быть удобнее использовать данные именно в таком формате.

Может быть и обратная ситуация, сервер может требовать данные в отличном формате от того, в котором они хранятся в схеме.

И обычно, разработчики, которые испытывают подобную необходимость, модифицируют данные после их получения, перед их использованием или перед их отправкой. Но с схемой формы, подобные модификации можно описать прямо в схеме.

Инициализация

В примерах ранее вы видели, что для создания схемы нужно вызывать статический метод create. Этот метод на вход может принимать объект, на основе которого схема может заполняться данными.

import { FormSchema } from '@yoskutik/mobx-form-schema';

export class BasicSchema extends FormSchema {
  name = '';

  surname = '';
}

const schema1 = BasicSchema.create();
console.log(schema1.name, schema1.surname); // '', ''

const schema2 = BasicSchema.create({
  name: 'Joe',
  surname: 'Dough',
});
console.log(schema2.name, schema2.surname); // 'Joe', 'Dough'

И ещё вы можете описать, как данные, получаемые в этом методе, должны быть предобработаны перед тем, как схема начнет их использовать. Для этого вы можете использовать декоратор @factory.

import { factory, FormSchema } from '@yoskutik/mobx-form-schema';

const createDate = (data: string) => new Date(data);

export class BasicSchema extends FormSchema {
  @factory.set
  set = new Set<number>();

  @factory(createDate)
  date = new Date();
}

const schema = BasicSchema.create({
  set: [0, 1, 2],
  date: '2023-01-01T00:00:00.000Z',
});
console.log(schema.set instanceof Set, schema.date instanceof Date);
// true, true

Представление

Т.к. каждая схема содержит в себе утилитарные данные и методы, вам может быть полезно получить объект, который бы содержал исключительно полезные данные из формы. Для этого вы можете использовать геттер presentation у схемы, который по умолчанию создает копию схемы без утилитарных методов и свойств.

import { FormSchema } from '@yoskutik/mobx-form-schema';

export class BasicSchema extends FormSchema {
  name = 'Joe';

  surname = 'Dough';
}

const schema = BasicSchema.create();
console.log(schema.presentation);
// {
//   name: 'Joe',
//   surname: 'Dough',
// }

Вы можете использовать декоратор @present для того, чтобы изменить содержимое объекта presentation. А также вы даже можете вырезать некоторые свойства из представления. Например, значение поля для ввода подтверждения пароля отправлять на сервер вы навряд ли захотите. Для этого можно использовать модификатор @present.hidden.

import { FormSchema, present } from '@yoskutik/mobx-form-schema';

const presentUsername = (username: string) => `@${username}`;

export class BasicSchema extends FormSchema {
  @present(presentUsername)
  username = 'joe-man';

  name = 'Joe';

  @present.hidden
  someUtilityProperty = 'utility data';
}

const schema = BasicSchema.create();
console.log(schema.presentation);
// {
//   name: 'Joe',
//   username: '@joe-man',
// }

В дальнейшем этот самый объект presentation вы можете использовать при отправке на сервер.

А как использовать в React?

Я создавал MobX Form Schema как пакет с минимальным набором зависимостей. Так что использовать React необязательно, главное, чтобы MobX в проекте был.

Но тем не менее я понимаю, что в большинстве случаев mobx используется именно с React, так что я подготовил пример использования своей библиотеки в React приложении. Но чтобы не раздувать статью, я просто приложу на них ссылки: документация, CodeSandbox.io, StackBlitz.com.

И просто, чтобы поддерживать ваш интерес, кратко покажу, как может выглядеть компонент для отображения формы с именем питомца из секции "Условная валидация".

export const ConditionalExample = observer(() => {
  const schema = useMemo(() => ConditionalSchema.create(), []);

  return (
    <form>
      {/* Т.к. вывод ошибок стандартизирован, TextField способен самостоятельно их отображать */}
      <TextField schema={schema} field="email" type="email" label="E-mail" />

      <CheckboxField schema={schema} field="doesHavePet" label="I have a pet" />

      {schema.doesHavePet && (
        <TextField schema={schema} label="Pet's name" field="petName" required />
      )}

      <button type="submit">Submit</button>
    </form>
  );
});

Получается, декораторы - та самая серебряная пуля?

Пытаюсь ли я своей статьей утверждать, что такой подход в разработке форм является единственно верным? Нет, конечно, серебряных пуль в разработке не бывает. Но мне действительно показалось, что такой подход упрощает процесс разработки. И, что немаловажно, почти не влияет на размер вашего бандла - не считая самого MobX, вся та функциональность, что я описал, хранится в пакете размером менее 4 Кб. А учитывая, что вам кода придется писать меньше, в объеме бандла вы можете только выиграть.

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

Однако, да, вам нужен MobX. По крайней мере в моей реализации. Если кто-то сделает нечто подобное для других стейт менеджеров, мне будет интересно на это посмотреть.

Конец

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

Жду вашего фидбэка в комментариях. Как вам вообще такой подход описания форм?

Ссылочка на npm пакет.

Пока.

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


  1. seregazolotaryow64
    03.10.2023 09:31

    Подобные декораторам вещи пробовал применять еще на практике и они работают на основе данных из бэкенда, где реализованы данные функции❤


    1. Yoskutik Автор
      03.10.2023 09:31

      Да, на бэкенде с декораторами разработчики давно привычные. Но на фронте это штука свежая


  1. SQwirty
    03.10.2023 09:31

    Интересная статья, очень подробно


  1. Alexandroppolus
    03.10.2023 09:31

    схема запускает расчет валидации в функции autorun из библиотеки MobX

    почему не computed?


    1. Yoskutik Автор
      03.10.2023 09:31

      Это сделано для оптимизации. Навешивать глобально computed на объект, который бы рассчитывал валидацию сразу для всех полей можно, но в таком случае при изменении одного поля, происходил бы полный перерасчет валидации всего объекта. А так можно удобно локализовать необходимые расчеты


      1. Alexandroppolus
        03.10.2023 09:31

        Зачем один? На каждое поле - свой компутед валидации


        1. Yoskutik Автор
          03.10.2023 09:31

          А, я понял о чем вы. Вы правы, можно через решить проблему с компьютед. Но т.к. в схеме есть возможность ручной валидации, в ней есть функции для расчета ошибки. И они же используются в autorun, чтобы просто переиспользовать логику.

          Однако, при помощью computed можно было бы переписать расчет условия валидации. Хотя функционально он бы все равно остался таким же, но кода так немного поменьше получилось бы


  1. AlexXYZ
    03.10.2023 09:31

    Скажите, а как на счёт поддержки такого способа в разработке?

    Мне такая валидация представляется весьма неполной потому что чтобы её повторить на сервере (сервер же тоже должен всё это проверить в том же объёме?) нужно писать отдельный код для клиента и для сервера, с учётом того, что они делают одно и тоже. (Задача клиента вроде как не перегружать сервер запросами на валидацию и быстрее сообщать пользователю, что что-то не так). Ну, как минимум, сервер делает не меньше. И синхронизация таких клиентской и серверной валидации для десятков полей уже весьма проблематична. Есть ли какие-то варианты кроссовости?

    P.S.

    Задача валидации очень непростая, хорошо, когда появляются какие-то инструменты с автоматизацией и когда кто-то потрудился её подробно описать, как ваша статья, например. Спасибо.


    1. Yoskutik Автор
      03.10.2023 09:31

      Я думаю, всегда, когда бэкенд пишется не на JavaScript придется дублировать логику валидации. Но если таки JavaScript используется, схемы можно без проблемы использовать и там. Они не привязаны к DOM'у)


      1. Yoskutik Автор
        03.10.2023 09:31

        Кстати, в случае использовании схемы и на бэке, и на фронте, можно на бэкенде схемы создавать в ручном режиме, т.к. реактивность там ни к чему. А ещё можно на бэкенде не использовать вообще @watch декораторы, если в них нет необходимости, а на фронте наследоваться от бэковых схем


      1. AlexXYZ
        03.10.2023 09:31

        Понял, спасибо. А нет ли возможности как-то "свести" валидаторы в общий список, чтобы хотя бы посмотреть их в общем? (понимаю, что, вряд ли, но вдруг что-то такое всё-таки есть? Думаю, что заказчики были бы счастливы, если бы был инструмент проверки валидации форм заявленным в ТЗ)


        1. Yoskutik Автор
          03.10.2023 09:31

          Вам интересны валидаторы, которые применяются на проекте или просто интересно, какими они должны быть? Во втором случае можно зайти на сайт с документацией, там довольно много примеров)


          1. AlexXYZ
            03.10.2023 09:31
            +1

            У меня есть собственная система валидаторов, работает и на клиенте и на сервере, но, как вы понимаете - самопал. Но частично решает проблему с синхронизацией кода для клиента и сервера одновременно, плюс для заказчика имеется отдельная таблица, по которой он может проверить и убедиться, насколько валидация соответствует ТЗ. Но хотелось бы найти что-то более совершенное, т.к. самописная несколько устарела - клиент на angularjs уже не совместим с новыми решениями. Поэтому и интересуюсь.

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