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`,这些将在未来版本中扩展。
附加信息
您可以在我们的 测试用例 中找到更多代码示例。