Angular 深入浅出三十天:表单与测试 Day16 - Template Driven Forms vs Reactive Forms

Day16

这段期间,我们用 Template Driven FormsReactive Forms 各自做了一个登入表单(静态)与被保人表单(动态),而且我们也都为这些表单写了单元测试整合测试,大家应该对於这两种开发表单的方式有一定的认知与体会。

因此,我们今天来将这两种开发表单的方式做个小结与比较,顺便沉淀一下这段时间的学习成果。

Template Driven Forms vs Reactive Forms

一般我们在对比这两个开发表单的方式时,会用以下三个面向来分析优劣:

  1. 开发难易度
  2. 维护难易度
  3. 测试难易度

开发难易度

开发难易度指的是,开发者分别使用这两种开发表单的方式开发同一个表单时的难易程度。

而这段时间,我也让大家跟着我一起分别使用这两种开发表单的方式开发了两个表单,大家可以自己在心里比较看看。

Template Driven Forms

Template Driven Forms 的方式很接近前端原始写法,资料的验证与限制都是使用 HTML 原生的表单机制,只是再额外加上 Angular 的资料绑定机制范本语法来处理,对於刚开始使用框架的初学者较为友善。

就像这样:

<input
  type="email"
  name="account"
  id="account"
  required
  pattern="\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b"
  #accountNgModel="ngModel"
  [ngModel]="account"
  (ngModelChange)="
    accountValueChange(accountNgModel.value, accountNgModel.errors)
  "
/>

Reactive Forms

Reactive Forms 的方式则是直接用程序来创建与操作表单物件 ─ FormGroup ,然後再把相应的 FormControl 绑定到画面上的表单栏位。

就像这样

const data = {
  account: ['', [ /* Validators... */]]
  password: ['', [ /* Validators... */]]
};
const formGroup = this.formBuilder.group(data);
<form [formGroup]="formGroup">
  <input
    type="email"
    id="account"
    formControlName="account"
  />
  <input
    type="password"
    id="password"
    formControlName="password"
  />
</form>

Template Driven Forms 相比,大部分的程序新手会觉得较为抽象,且会有相对来说比较多较困难、较不习惯的观念要熟悉,学习成本较高

此外,如果有遇到更复杂的连动逻辑、更动态的表单,会比较需要对 RxJS 有更进一步的认知。

开发难易度小结

大部分刚学 Angular 的朋友应该都会觉得 Template Driven Forms 比较简单,不过虽然一开始做的时候好像很简单且自然,但表单一复杂起来,那沱 HTML 实在是会有点惨不忍睹。

这还是因为我们这段时间所做的表单其实很阳春、很简单,如果我们要做的是有着更复杂的连动逻辑、更动态的表单,光想到要处理一堆事情就头皮发麻、冷汗狂流。

Reactive Forms 初接触时好像感觉很难,但随着熟悉度的提升,大家一定会觉得它越来越好用,用它来开发又快又轻松,尤其当要做的表单越复杂、越动态时,更能体会它的美好。

我自己一开始使用 Angular 的时候也是只会使用 Template Driven Forms 的开发方式来开发,甚至还实作过颇为复杂的动态表单。直到我学会了使用 Reactive Forms 的开发方式之後,才发现之前做的表单有多麽可怕。

维护难易度

我这边的维护难易度主要指的是以扩充性重用性这两种面向来比较这两种开发方式的难易程度。

Template Driven Forms

扩充性来说,假如我们一起开发过被保人表单需要新增一个产品栏位,并增加年龄与产品之间的互动逻辑时,除了要在 Template 上新增一个产品栏位与其必须的验证之外,在 Component 里也需要加上相应栏位有变动时,与其他栏位的互动、提示讯息的逻辑。

重用性来说,假如今天有同样的表单栏位与验证逻辑要在别的地方使用,但是画面可能会长得很不一样,抑或是只是其他表单里的其中几个栏位,这时为了要重用也会需调整不少程序码。

好一点的情况可能只需要将这些栏位包装起来并增加 input/output 的逻辑,差一点的情况大概就连重用都很困难,只能尽量把能抽的逻辑抽离,又或者把他们抽成最小最不会有影响的 Component 来使用。

Reactive Forms

扩充性来说,不管是要新增栏位还是调整结构,由於 Reactive Forms 本身就是用程序来建立表单,所以基本上都只需要在程序里处理好,而 Template 就只是很简单的增加该增加的栏位、并给予相应的绑定而已,如 formContorlName="xxx" ,轻松自在。

重用性来说,这件事在 Reactive Forms 看来更是小菜一碟。本来就是用程序建立表单的它,本身基本就具备非常良好的重用性,就算要把原本的表单抽成最小单位的 FormControl ,也只是像乐高积木一样,需要的时候再组合起来就好。

维护难易度小结

简单来说, Template Driven Forms 的开发方式有点像在煮义大利面,煮完之後就很难去分离,虽然面依然是面、酱汁依然是酱汁,但面已饱富酱汁、酱汁也难以再还原回原本的食材。

Reactive Forms 就像是乐高积木,具有丰富的可变性与卓越的弹性,你想要怎麽组合都可以,就算拼成乐高版的义大利面,也是说拆就拆、说散就散。

虽然整体来说还是要看个人的功力,但就同一个人用这两种方法来比较的话,应该还是会有差不多的结果。

测试难易度

测试严格来说应该是维护中的一环,因为每当程序码有调整时,或者是需求有调整时,都有可能会影响到测试。

不过此处特别提出来比较主要是想要只在撰写测试这件事情上来比较这两种方式的难易度,尤其是我们这段时间总计写了八篇的测试,大家应该会比较能感同身受。

Template Driven Forms

Template Driven Forms 在撰写测试上也因为其方式很接近前端原始写法的关系,我觉得还算好写,只要检查元素的属性与其值即可。

但由於 Template Driven Forms 比较不可控,且其更新时机是非同步且比较不可预测的关系,造成想要把它的测试写得很好并不容易。

就拿我们写过的测试来说,我们在第七天第十三天时,都有着过相同的问题,而这问题,说不定其实是我写不好,并不是框架本身的问题。

所以说,在某些特定情境下的测试案例,要写得好其实并不容易。

Reactive Forms

Reactive Forms 的更新时机基本上是同步且可预测的,有什麽变化就可以直接验证到,毕竟它本身就是用程序来建立表单,可控性很高。

同样地拿我们写过的测试来说,相信大家应该都没有遇到什麽问题,不知道大家是否也觉得它的测试案例相对好写呢?

测试难易度小结

撰写测试的难易度其实很大程度地影响了开发人员是否会持续撰写或维护测试程序的意愿。

所以对开发人员来说,当然是越容易越好。

本日小结

今天主要是想明确地让大家知道 Template Driven FormsReactive Forms 之间的不同与更清楚地对比,让大家未来在遇到需要制作表单的情境时,可以根据需求来选择最适合的方式。

下表总结了 Template Driven FormsReactive Forms 的不同之处:

  Reactive Forms Template Driven Forms
表单模型的设置 清楚的,在 Component 里建立 隐晦的,用 Directive 建立
资料模型 有结构性且不变的 松散且容易改变
可预测性 同步的 非同步的
表单验证方式 用函式验证 Directive 验证

虽说这段时间有分享如何使用 Template Driven Forms 的方式来开发表单,不过我个人在遇到要制作表单的情境时,其实都是选择用 Reactive Forms 的方式来开发,因为实在是真的太好写了!

此外,我们的後续文章也将不会再分享 Template Driven Forms 的开发方式,而是会用 Reactive Forms 的方式来分享更多更进阶的用法,让大家可以因应更复杂、更动态的情境。

如果你有任何的问题或是回馈,还请麻烦留言给我让我知道,感谢大家!


<<:  部署model on seldon(MinIO)

>>:  JS中的排序法_上

DBA 训练营 - SQL Server 资料库管理入门

数据资料与 Azure SQL 热潮来袭 升级资料库专业技能 Are You Ready? DBA ...

Day 11. Zabbix 网页介面介绍

今天要跟大家介绍 Zabbix管理介面的侧边拦几大功能,希望可以快速让大家找到自己想看的内容,主要分...

模型的内容04 def main()

上一章节研究完class net(…),这一章节我们继续研究 def main(args)这部分。 ...

新新新手阅读 Angular 文件 - Day01

创建一个 Angular 的专案 这篇是记录安装 angular 的 CLI 的方法。 预备环境 首...

Flutter体验 Day 28-flame JoystickComponent

flame JoystickComponent 昨日我们使用 SpriteComponent 建构出...