License: MIT ci pub package

FPFormz

一个基于 Fpdart 的函数式输入验证库,灵感来自 Formz

特点

FPFormz 在大多数方面与原始的 Formz 库相似,但也存在一些显著的差异。

  • FPFormz 允许为输入值和验证值指定不同的类型,这在使用非字符串类型值(例如 `int`、`enum`、值类等)时非常方便。

  • 它将验证值和错误作为函数式构造(如 `Either` 或 `Option`)暴露出来,从而更容易以声明式的方式进行操作。

  • 它还提供了一种将验证逻辑编写为 mixin 的方法,您可以组合它们来处理更复杂的用例。

安装

您可以通过在 `pubspec.yaml` 中添加以下条目来安装 PFFormz:

# pubspec.yaml
dependencies:
  fpformz: ^0.1.0

入门

FormInput 及其派生类

要定义一个可验证的输入,您需要编写一个继承自 `FormInput<V, I, E>` 的类,其中泛型参数分别代表结果值、输入值和潜在错误的类型。

class AgeInput extends FormInput<int, String, ValidationError> {
  const AgeInput.pristine(name, value) : super.pristine(name, value);

  const AgeInput.dirty(name, value) : super.dirty(name, value);

  Either<ValidationError, int> validate(String value) =>
      Either.tryCatch(() => int.parse(value),
              (e, s) => ValidationError(name, '$name should be a number.'));
}

声明您的输入后,您可以使用 `pristine` 或 `dirty` 构造函数来创建实例。

void example() {
  // To create an unmodified ('pristine') input instance:
  final age = AgeInput.pristine('age', '');

  // Or you can create a modified ('dirty') input instance as below:
  final editedAge = AgeInput.dirty('age', '23');

  print(age.isPristine); // returns 'true'
  print(editedAge.isPristine); // returns 'false'
}

您可以将验证信息作为函数式构造或可空值来访问。

void example() {
  print(editedAge.isValid); // returns true
  print(editedAge.result); // returns Right(23)
  print(editedAge.resultOrNull); // returns 23
  print(editedAge.error); // returns None
  print(editedAge.errorOrNull); // returns null

  print(age.isValid); // returns false
  print(age.result); // returns Left(ValidationError)
  print(age.resultOrNull); // returns null
  print(age.error); // returns Some(ValidationError)
  print(age.errorOrNull); // returns ValidationError
}

由于大多数输入组件都将用户输入视为 `String` 实例,您可以继承 `StringFormInput` 来简化类型签名。

class NameInput extends StringFormInput<String, ValidationError> {
  const NameInput.pristine(name, value) : super.pristine(name, value);

  const NameInput.dirty(name, value) : super.dirty(name, value);

  @override
  String convert(String value) => value;

  @override
  Either<ValidationError, String> validate(String value) =>
      value.isEmpty
          ? Either.left(ValidationError(name, 'The name cannot be empty.'))
          : super.validate(value);
}

Form

与 Formz 一样,您可以创建一个表单类来容纳多个输入字段并一起验证它们。

class RegistrationForm extends Form {

  final NameInput name;
  final EmailInput email;

  const RegistrationForm({
    this.name = const NameInput.pristine('name', ''),
    this.email = const EmailInput.pristine('email', '')
  });

  @override
  get inputs => [name, email];
}

然后,您可以使用类似于 `Formz` 的 API 来验证它,例如 `FormInput`。

void example() {
  final form = RegistrationForm();

  print(form.isPristine); // returns true
  print(form.isValid); // returns false

  print(form.result); // it 'short circuits' at the first error encountered
  print(form.errors); // but you can get all errors this way. 
}

Mixins

您还可以将可重用的验证逻辑编写为 mixin。

@immutable
mixin NonEmptyString<V> on FormInput<V, String, ValidationError> {
  ValidationError get whenEmpty => ValidationError(name, 'Please enter $name.');

  @override
  Either<ValidationError, V> validate(String value) =>
      value.isEmpty ? Either.left(whenEmpty) : super.validate(value);
}

如下所示,通过将它们添加到 `BaseFormInput` 或 `StringFormInput` 中来构建具体的输入字段。

class EmailInput extends StringFormInput<Email, ValidationError>
    with EmailString, NonEmptyString {
  const EmailInput.pristine(name, value) : super.pristine(name, value);

  const EmailInput.dirty(name, value) : super.dirty(name, value);

  @override
  Email convert(String value) => Email.parse(value);
}

建议将每个验证逻辑拆分为一个单独的 mixin,而不是将所有逻辑都放在一个输入类中,以最大程度地提高代码复用性并实现关注点分离(即 SOLID 原则中的“S”)。

FPFormz 还附带了一小系列现成的 mixin,例如 `NonEmptyString`、`StringShorterThan`,这些将在未来版本中扩展。

附加信息

您可以在我们的 测试用例 中找到更多代码示例。

GitHub

查看 Github