扩展内置 Form 引擎

Reactive Form 的内置引擎被封装在 Form 模型内,除了集成常用的成员类型, 还提供了一些帮助扩展成员类型的方法,以便于开发人员可以根据需要,丰富内置引擎的能力。 在本章中,我们将一起熟悉这些方法,并通过具体的示例来学习如何使用。

注册成员类型

在介绍 Reactive 的实现原理时, 我们提到过可以向 Reactive 实例中注册成员类型。 对于内置引擎而言,成员类型也是被注册到 Reactive 实例中, 不过,Form.install() 方法对 reactive#install() 方法进行了装饰, 提供了额外的能力,这些能力特性包括:

分组设定

在使用 Form.install() 注册内置引擎的成员类型时, 可以通过 group 属性对类型进行分组, 分组后的类型可通过 Form.group() 方法,按分组名称查询到注册时的配置。 当成员类型较多时,分组设定就为归类成员类型提供了帮助,我们通过下面的示例来具体说明。

引用范围设定

为了保证成员间引用的灵活性,Reactive DSL 在解析插值符中的引用路径时,并不对引用范围做限制,这意味着任何成员的任何属性间都可以产生引用。 对于一些表单引擎而言,成员的引用范围因输入项类型不同可能有所差异, 为了支持此类场景,Form 模型提供了设定成员引用范围的能力。

在使用 Form.install() 注册类型时, 可以通过 providers 属性对该类型成员的引用范围进行设定, 设定后可通过 Form.providers() 方法, 查询到该类型下某成员属性可引用的其它成员及属性范围。

基于这两个方法组合使用的能力,开发人员可以对不在范围内的引用进行适当的处理, 处理方式多种多样,并没有统一定论,比如,可以通过添加钩子函数, 向成员的存储空间中写入错误信息,或在产生引用的属性上进行过滤,甚至直接将产生引用的属性置空,等等。 下面,我们使用直接置空属性设定值的方式做例。

需要提及的一点是,对于注册时未设定 providers 的类型(如内置引擎默认集成的各输入项类型), 调用 Form.providers() 将返回默认引用范围。 默认可被引用的成员是所有 Fieldset 子级中含有 value 属性且不与被查询成员产生循环引用的成员, 默认可被引用的属性则是 value。默认引用范围这样设定的原因是,大部分表单引擎中,成员属性的引用多发生在输入项的值之间。

另外, Form.providers() 方法在 Reactive Form 内置引擎配套的 UI 组件中,也起到了一定的辅助作用,后续的组件教程中将会介绍。

校验方法范围设定

在使用 Form.install() 注册类型时, 可以通过 validators 属性对可用于该类型成员的校验方法范围进行设定, 设定后可通过 Form.validators() 方法, 查询到该类型下某成员可用的校验方法。

与处理引用范围异常类似,组合使用这两个方法,可对成员属性包含不合规校验方法的情况, 进行适当的处理。下面,我们仍以直接置空属性设定值的方式做例。

对于注册时未设定 validators 的类型(如内置引擎默认集成的各输入项类型), 调用 Form.validators() 将返回默认的可用校验方法范围。 默认范围的匹配规则是,如果 schema 定义中 value 属性的 validation 指定了 dataType, 则返回所有可用于该 dataType 的校验方法,否则只返回 presence 校验方法。

比如,内置引擎集成的 TextField,其 schema 中对 value 属性的定义是:

{
  prop: 'value',
  validation: [{ dataType: 'String' }],
  ...otherDefinations
}

因为 validation 包含了 {dataType: 'String'},所以,调用 Form.validators() 查询 TextField 成员的可用校验方法范围,就会返回所有可应用于 String 数据类型的校验方法。

另外, Form.validators() 方法在 Reactive Form 内置引擎配套的 UI 组件中,也起到了一定的辅助作用,后续的组件教程中将会介绍。

计算方法范围设定

当前版本暂不提供设定成员属性计算规则范围的内置方法,后续版本中将逐渐添加。 现阶段,开发人员如有需求,可以先自行实现。

注册输入项类型

为了便于注册输入项类型,Form.Field 模型提供了,构建输入项类型配置对象的方法 Form.Field.schema.extend(),还提供了可复用的 schema 配置以及钩子函数。 下面将介绍具体的使用方法。

复用 schema

在输入项类型的注册过程中,很多属性的 schema 定义都是雷同的, 比如标题数据类型为字符串、校验规则默认为空,等等。 Form.Field 内置了输入项类型常用的 schema, 这些 schema 可通过 Form.Field.schema.use() 方法和 Form.Field.schema.extend() 方法直接复用或扩展后复用。 我们来看下面的示例。

通过示例可以看到,useextend 这两个方法都会返回一个属性的 schema 定义。 这样,我们就可以在 schema.props 数组中,把不同属性的定义,按需拼装到一起了。 内置的 schema 属性范围包括 namelabelordervalueoptionsplaceholdereditablevisiblevalidationcalculation,读者可通过 use 方法查看它们的定义。

复用钩子函数

在介绍 Reactive 时,介绍过 Reactive 为了方便扩展, 内置了一些可复用的钩子函数。同样的,Form.Field 也内置了可复用的钩子函数, 这些钩子函数主要应用于输入项类型,可通过 Form.Field.hooks.use() 方法复用。 我们在下面的示例中将逐一展示这些钩子函数的作用和用法。

ENABLE_VALUE_CALCULATION

ENABLE_VALUE_CALCULATION 添加了输入项成员 calclation 属性和 value 属性的联动逻辑, 每当 calculation 当前值更新时,会使用该值设定 value 属性。具体用法如下例。

ENABLE_VALUE_VALIDATION

ENABLE_VALUE_VALIDATION 添加了输入项成员 validation 属性和 value 属性的联动逻辑, 每当 validation 当前值更新时或 value 当前值更新时,会使用前者校验后者,并清空或写入 value 错误信息。 具体用法如下例。

ENSURE_VALUE_WITHIN_OPTIONS

ENSURE_VALUE_WITHIN_OPTIONS 添加了输入项成员 options 属性和 value 属性的联动逻辑, 每当 options 当前值更新时或 value 当前值更新为非空值时,都会校验 value 是否在 options 范围内, 并清空或写入 value 错误信息。具体用法如下例。

Form.Field.hooks.default ()

当我们注册的输入项类型比较多以后,会发现有一些内置钩子函数的复用频率很高, 为了便于批量复用,Form.Field.hooks.default() 方法聚合了高频复用的钩子函数, 当该方法被调用时,会返回一个由这些钩子函数构成的数组,可以在注册输入项类型时直接使用。 其返回结果中包含的钩子函数按数组下标顺序依次为:

Reactive.hooks.use('BLOCK_INVALID_PARENT', {accept: 'Fieldset'})
Reactive.hooks.use('ENABLE_SCHEMA_DEFAULT')
Form.Filed.hooks.use('ENABLE_VALUE_CALCULATION')
Reactive.hooks.use('ENABLE_SCHEMA_VALIDATION')
Form.Filed.hooks.use('ENABLE_VALUE_VALIDATION')

开发人员可以在这些钩子函数基础上按需追加,对特定输入项类型的实现更多定制。

自动注册子表单输入项类型

上一章,我们介绍过, 内置引擎集成的输入项类型经过自动化元编程处理后,可在子表单中嵌套。 这是因为内置引擎集成的输入项类型在注册前,使用 Form.Field.extend() 方法进行了装饰,装饰过程包括向 group 追加 Field 分组名称, 以便在注册时被识别,从而触发子表单输入项类型的生成过程,自动注册一个 Subform*Field 类型。

多数情况下我们希望自定义注册的输入项类型也可以嵌套进子表单, 那么就也可以使用 Form.Field.extend() 方法来装饰。 具体用法如下例。

小结

  • 内置引擎中的成员默认可引用的是所有 Fieldset 子级中含有 value 属性且不与自身产生循环引用的成员的 value 属性。
  • 内置引擎中的成员默认可用校验方法范围是,如果 schema 定义中 value 属性的 validation 指定了 dataType, 则为所有可用于该 dataType 的校验方法,否则为 presence 校验方法。
  • Form.Field 内置 schema 可通过 Form.Field.schema.use() 方法和 Form.Field.schema.extend() 方法直接复用或扩展后复用。
  • Form.Field 内置钩子函数可通过 Form.Field.hooks.use() 方法复用。
  • Form.Field.hooks.default() 方法聚合了注册输入项类型时高频复用的钩子函数。
  • Form.Field.extend() 方法来装饰的输入项类型,在注册时会自动注册一个对应的子表单输入项类型。