入门示例

Reactive Form 有多种使用维度 —— 从不同层级进行扩展或直接使用内置 Form 引擎。 相应的,使用方式的灵活程度和复杂程度也不同。 在这一节里,我们从最简单的使用方式入手,直接使用内置引擎,构建一个具备校验能力的表单, 并模拟表单配置和数据的存储,使用持久化数据回填。

配置表单

内置 Form 引擎的 Builder 组件提供了可视化配置表单的功能, 其 props 接收一个 Reactive 实例, 实例状态的变更会反应到组件的绘制状态上,相应的,对组件执行操作也会调用相应的实例方法更新实例状态。

在上面的代码片段中,因为没有向 Reactive 实例添加任何表单成员,所以绘制出的组件只有一个添加按键。 Reactive 实例支持注册成员类型,且只允许添加已注册类型的成员, 接下来,我们从内置 Form 引擎中选取一些已注册的类型,添加成员到表单中。

reactive#seed()reactive#add() 是添加 Reactive 成员的主要方法,添加时可以按需设定成员属性。 对于已存在的 Reactive 成员,可以使用 reactive#set() 方法更新属性。 但是 type 属性除外,因为 type 是区分已注册成员类型的标识,成员创建后不可变更。

上例中,“经营状态”的初始值被设定为 'openning',匹配到了“在业”选项,所以“在业”选项显示了勾选状态。 Reactive 成员的属性值可以被设定为某一固定值,也可以与其它成员联动,按条件设定,请看下面的代码片段。

我们为“经营范围”设定了因条件而异的校验逻辑,添加的联动规则比较简单,只包含对“经营状态”一个成员的引用。 实际上,Reactive 提供了 DSL 支持, 可以解析完整的条件语句、与或逻辑,以及上下文成员引用,在后续的教程中将逐渐展开。

在上文中,对表单的配置全部使用 Reactive Form 模型层提供的 API 实现, 这些配置也可以通过 Builder 组件可视化实现。 读者可以回到第一个代码示例中,尝试使用可视化操作的方式配置一遍, 更直观地感受 Reactive Form 构建表单配置的过程。

填写表单

内置 Form 引擎的 Renderer 组件提供了绘制表单的功能, 绘制出的表单可被填写,其 props 同样接收一个 Reactive 实例, 每个成员 value 属性的变化会反应到组件的绘制状态上, 相应的,在绘制出的组件上填写的值也会更新到对应成员的 value 属性。

Reactive Form 并不刻意区分配置过程和填写过程, 无论哪种过程都是对 Reactive 成员的属性赋值,只不过在配置过程中需设定的属性较多, 而填写表单过程中大多只设定 value 属性。 我们通过下面的示例来说明这一点,勾选“经营状态”的不同选项并填写“经营范围”,观察“经营范围”的变化。

当“经营状态”被勾选为不同选项时,其 value 属性被更新,并触发了“经营范围”不同的 validation 逻辑。 可以看到,在上面的示例中,除了在代码末尾换用 Renderer 组件来绘制表单外,我们未对其它代码做任何修改, 就能得到一个可填写的表单,配置的各种属性也可以在不经过任何处理的情况下直接生效。 以模型为核心驱动不同视图的设计原则是 Reactive Form 不必区分配置过程和填写过程的前提, 类似预览之类的功能可以很方便的实现。

配置和数据的持久化

在实际的场景中往往涉及到表单配置和表单数据的持久化存储,通常这是两个过程, 下面我们就通过调用 reactive#collect() 方法来获取 Reactive 成员树的状态,并模拟对表单配置和表单数据的持久化存储。

点击上例中的“保存表单配置”按键,表单配置会存储到 sessionStorage 中, 在下一个示例中,我们将读取存储的表单配置来绘制可填写的表单,并且将表单数据也存储到 sessionStorage 中, 读者可尝试修改上例中的表单配置并保存,之后点击下例中“Rerun”按键体验配置变化后的表单。

reactive#collect() 方法的返回结果主要包含两部分:成员属性的设定值 raw.values 和成员属性的当前值 values。 同一成员同一属性的设定值和当前值有可能不同,这是因为其设定值可能存在联动关系, 在这种情况下,其当前值取决于参与联动的其它成员,是动态运算得出的。如“经营范围”的 validation 属性, 读者可以尝试修改代码,查看在“企业状态”勾选不同选项时,“经营范围”的 validation 属性设定值和当前值的区别。

在上例中,无论是使用表单配置还是表单数据填充 Reactive 实例, 使用的都是 reactive#seed() 方法。 需要注意的是,此方法执行时按照 json 结构层级来创建成员或更新成员属性, 如果不同版本的持久化配置间有差异,seed 方法创建的成员树结构可能不一致, 那么跨版本持久化的表单数据回填时可能会不匹配成员树结构, 应对此类情况应做好数据兼容或使用 reactive#set() 方法按照固定的匹配规则填充数据,通常来说, 可按照 name 属性来匹配,在后续教程中将陆续展开。

异常处理

Reactive 的成员属性值在被更新后,会立即执行值的合规性校验,依据校验结果的不同, 成员属性的错误信息会被清空或更新。 在通过 reactive#collect() 方法获取成员树状态时, 各个成员的错误信息也会被返回,我们通过下面的示例具体说明。

填写异常

在下方示例中,保存表单数据前我们查询了成员树的错误信息,并在有错误的时候拒绝保存。 读者可以填写不符合“经营范围” validation 要求的值,点击“保存表单数据”观察校验未通过时的行为。

配置异常

表单的配置过程也可能产生不合规的属性值,内置引擎的 Builder 组件会实时显示错误信息, 与上方示例类似,可以在“保存表单配置”前查询成员树的错误信息,只在没有错误时保存配置。 如果需要在某些属性不合规的情况下依然可以保存配置,则需对错误信息进行过滤和判断。 比如下面的示例,当成员 value 属性的初始状态不符合 validation 要求时,依旧能够保存配置。

小结

  • reactive#seed() 按照 json 结构层级创建、更新成员。
  • reactive#add() 可向任意成员依次添加子成员。
  • reactive#set() 可按照成员 id 更新属性。
  • reactive#collect() 用于搜集成员树状态, 返回结果包括成员属性的设定值 raw.values、当前值 values,以及错误信息 errors
  • 当成员属性的设定值包含 DSL 插值符 << >> 引用时,其当前值根据联动的成员状态,进行动态运算得出。
  • Reactive 实例只允许添加已注册类型的成员,成员的 type 属性是区分类型的标识,在创建后不会被修改。