.Net Core应用框架Util介绍(五)

上篇简要介绍了Util在Angular Ts方面的封装情况,本文介绍Angular封装的另一个部分,即Html的封装。 标准组件与业务组件   对于管理后台这样的表单系统,你通常会使用Angular Material或Ng-Zorro这样的UI组件库,它们提供了标准化的UI组件。   标准组件将Ts封装起来,以特定标签和属性的方式提供使用。   业务组件使用标准组件拼凑页面,并从服务端API获取数据绑定到页面上。   可以看出,标准组件是业务开发的基础,我们必须将标准组件的开发效率提升到极致。 使用标准组件的问题   直接使用原生标准组件有什么问题呢? 复杂的Html结构   现代流行的UI组件库,为了构造美观大气的视觉效果及增强组件的功能特性,一个组件需要组装多个Html元素来表达。   在带来美观视觉体验的同时,也导致了Html结构变得很复杂。   Angular Material是Google以Material设计风格开发的UI组件库。   我们来看一个Angular Material文本框的例子。 复制代码 复制代码   你看到了Angular Material文本框并不是一个input标签,input标签嵌套在mat-form-field标签内。   这看上去并不算复杂,不过它只是最简单的情况,让我们增加两个特性。 复制代码 哈哈 复制代码   我们在文本框的下方添加了提示文本,并在文本框右侧加了个按钮,你可以点击这个按钮清空文本框的内容。   你应该观察到Html结构变得稍微复杂了,让我们再添加两个特性。 复制代码 $   充值金额 最大金额不能超过10元 这是一个必填项 复制代码   现在在文本框的左侧加了一个美元符号,在文本框右侧添加了后缀“元”,另外添加了必填和最大值验证。   这还只是一个不太复杂的文本框,Html居然这么长。     组件标签结构成为前端业务开发的第一个关注点。 繁琐的数据绑定   如果要绑定一些可选项到下拉列表,一种办法是硬编码。 复制代码 A B C 复制代码   这是具有三个选项的下拉列表。   如果我们要绑定56个民族,就需要硬编码56个选项,这确实可行,不过一个下拉框就60几行,占地太广,复制粘贴也不方便。   另外,下拉选项可能是动态的,这些可选值存储在数据库中。   数据绑定大多从服务端获取数据,绑定到组件上。   Angular提倡将数据访问与组件分离,这个设计理念被Angular Material这些标准组件库所遵循。   为了绑定数据,你首先需要发送一个Http请求,从服务端获取Json数据,转换为Ts对象,然后通过Angular提供的循环语法绑定上去。 复制代码 {{food.viewValue}} 复制代码   Angular Material下拉列表能够分组,它与普通下拉列表的Html结构不同,如果服务端返回的数据格式不太友好,绑定起来将更加困难。 复制代码 -- None -- {{pokemon.viewValue}} 复制代码   下拉列表并不是唯一需要数据绑定的组件,还有一些组件也需要,且它们更加复杂,比如树型控件,表格控件,树型表格控件等。   数据绑定成为前端业务开发的第二个关注点。 低效的验证   验证是业务健壮性的基本保障,Angular Material表单组件提供了基本的验证方法。 复制代码 复制代码   上面演示了设置必填项的方法,它相当简单,只要把required加到input标签上就好了。   遗憾的是,文本框虽然得到了验证,但却没有显示出任何错误提示消息。   通过添加一个mat-error标签,可以显示指定错误提示。 复制代码 这是一个必填项 复制代码   如果组件上有两个验证条件,你需要添加两个mat-error标签。 复制代码 最大值不能超过10 这是一个必填项 复制代码   注意,为了让提示消息只在特定验证条件失败时才显示,你需要在mat-error标签上进行验证状态判断。   如果现在组件包含5个验证条件,mat-error和它上面的判断条件将变得相当复杂。   另一方面,客户端脚本验证只是为了提升用户体验,用户可以绕过界面直接请求你的服务端,所以真正的验证必须在服务端完成。   这样一来,验证需要在客户端和服务端编写两次,这造成了双倍的工作量。   当需求发生变动,服务端和客户端的验证很难同步更新,维护变得更加困难。   验证成为前端业务开发的第三个关注点。 解决方案   如果开发的时候,既不用关心Html的结构,又不用关注数据怎么绑定,验证还能自动完成,甚至连标签和它上面的属性也不用记忆,这就最理想不过了,该如何实现呢? 用Angular组件包装标准组件   首先我们需要用Angular组件对标准组件进行包装,以方便功能扩展,这个自定义组件称为包装器。 封装Html复杂结构   我们把标准组件的Html标签包装起来,以属性的形式提供访问。 复制代码 哈哈 复制代码   把上面标签包装后变成这样。   mat-hint标签现在被转换为hint属性,通过showClearButton属性来控制是否显示清空按钮,大幅提升了组件的易用性。 约定前后端数据格式   不论下拉列表,还是表格,甚至树型控件这些需要数据绑定的组件,都有一定规律可循。   当你一遍又一遍的复制粘贴,仔细观察这个机械乏味的绑定过程,不难抽取出公共元素,形成前后端数据绑定的通用数据格式。   一旦抽取出前后端通用数据格式,你只需将业务数据转换为通用格式,发送到客户端就自动绑定完成。 将数据操作内置到包装器   如果你曾经使用过EasyUi这样的组件库,定会发现它的数据绑定功能十分强大,这是因为它把数据操作内置到了标准组件中。   基于Angular低耦合设计原则,Angular Material标准组件并不会直接请求服务端,任何数据绑定工作都需要你手工完成,不过我们可以将数据操作内置到包装器。   一旦封装完成,数据绑定变得非常简单,比如设置一个url属性即可,服务端返回约定的数据格式。 用TagHelper封装Angular组件   Angular包装器组件,大幅简化了标准组件的使用,但它提供的依然是Html,而自定义Html标签和属性没有什么提示,这意味着你如果记不住这些API,就需要随时欣赏API文档。   TagHelper终于闪亮登场。 强类型代码提示和编译时检查   一旦把Html标签封装成TagHelper,就可以跟API文档拜拜了,把代码提示点出来,慢慢选,只要你知道该组件确实有这功能,哪怕印象有点模糊也没关系。   Html标签和属性的拼写错误也将与你无缘,VS大哥会为你把关,代码健壮性将大幅提升。 Lambda表达式元数据解析   很多人已经认识到HtmlHelper或TagHelper的好处是强类型提示,不过这个认识还很肤浅。   TagHelper真正的威力来自Lambda表达式元数据解析,它提供了一个统一的抽象方式,自动设置表单组件的常规属性、验证,甚至数据绑定。   对于Angular Material表单组件,通常需要设置以下常规属性:   控件名称 name   占位文本 placeholder   双向绑定 ngModel   常规验证:   必填项验证 required   Email验证 email   最小长度验证 minlength   最大长度验证 maxlength   最小值验证 min   最大值验证 max   正则验证 pattern   几乎所有表单组件都需要设置这三个常规属性,而文本框更需要进行多种验证,虽然这些操作并不复杂,但由于一个表单界面包含很多组件,每个组件都要挨个设置,既浪费时间又枯燥乏味。   如果能够自动化设置这些常规属性和验证属性,虽然从单个组件看并不起眼,但从整个项目的角度,能大幅提升生产力。   Lambda表达式元数据解析,通过读取C#属性的类型信息以及相关的特性,能够自动化设置三大常规属性,以及对文本框实施多种验证,还解决了客户端与服务端验证无法同步的难题。   一旦用上Lambda表达式,界面标签将变得干净整洁,你的关注点将迅速转移到业务上。   上面的TagHelper标签生成的结果Html如下。 复制代码 复制代码   for指向了ApplicationDto对象的Code属性,下面是Code属性的定义。   从Code属性定义可以解析出该组件需要设置的常规属性和验证属性。   上面演示了文本框组件,对于单选框,多选框,下拉框等表单组件,都可以使用相同的方式,一个for属性,基础工作已经完成。 封装的弊端   看了前面的解决方案,你知道经过几层高强度封装后,组件将变得简单易用,不过在将这些方法应用到你的项目之前,你需要对这些方法有更深的了解。   任何事物都有其两面性,所谓此消彼长,在组件变得更加简单易用的同时,它的灵活性也在降低。   包装器组件将Html结构封装起来,这会导致组件不再支持模板化,如果某个功能在你的包装器中未实现,那么不能通过在包装器标签内嵌套HTML的方式组合出新的功能。   封装包装器组件有相当多的讲究,特别是Angular Material这样的组件库在功能上几乎无法与EasyUi或Ext等企业级UI库相提并论,你必须在易用性和灵活性间进行平衡,对于像表格这样的重量级组件,很难封装到完全满足业务需求,这种情况下,你必须为其保留模板化能力。   另一方面,封装后的傻瓜式TagHelper,很容易把程序员惯坏,开发常用功能风升水起,一碰到超出框架范围的需求就变得束手无策,因为他们从来没有学习过原生的知识。   你团队的主力开发人员必须对原生技术有系统了解。   一旦功能超出框架范围,你必须有能力扩展框架,在必要的时候,直接使用原生Html进行开发,这时候你更能体会到TagHelper与Html混合编程的好处,既提升了常规功能的开发效率,又满足了复杂功能对操作体验的需求。 Util组件介绍   下面简要介绍Util中封装的几个常用组件,它们来自Angular Material或PrimeNg组件库。 文本框   前面已经展示过文本框的用法,除了常规属性设置和验证以外,for指向属性的数据类型会影响生成的文本框类型,比如属性为日期类型,文本框会变成一个日期选择控件。 复制代码 /// /// 创建时间 /// [Display( Name = "创建时间" )] public DateTime? CreationTime { get; set; } 复制代码   生成的Html结果如下。   下面再演示一下数值类型,添加了最大和最小值验证,并设置前缀文本和后缀图标。 当属性为数值类型时,文本框只能输入数字。 复制代码 /// /// 金额 /// [Required( ErrorMessage = "必须填写金额" )] [Range(10,50,ErrorMessage = "有效金额在10到50之间")] [Display( Name = "金额" )] public decimal Money { get; set; } 复制代码   生成的Html结果如下。 复制代码 复制代码   来看看执行效果。 下拉列表   下拉列表的封装,重点在于数据绑定。 绑定枚举   下面演示如何把民族枚举绑定到下拉列表。   C#代码如下。 民族枚举 复制代码 /// /// 民族 /// [Required( ErrorMessage = "必须选择一个民族" )] [Display( Name = "民族" )] [DataMember] public Nation Nation { get; set; } 复制代码   TagHelper代码如下。   生成的Html如下,可以看出,民族可选项被硬编码到Html标签中。 复制代码 复制代码   执行效果如下。 绑定服务端数据   为了绑定服务端数据,必须约定通用数据格式,对于下拉列表,服务端C#是由Util.Item来完成的。 Util.Item   客户端Typescript定义了对应的结构。 SelectItem   下面来演示一下用法。   先把Nation属性的类型改成int。 复制代码 /// /// 民族 /// [Required( ErrorMessage = "必须选择一个民族" )] [Display( Name = "民族" )] [DataMember] public int Nation { get; set; } 复制代码   在WebApi控制器中,添加一个方法,用来获取民族枚举可选项。   通过Util.Helpers.Enum.GetItems方法可以提取出枚举项列表,返回值为List,这正是我们约定的标准格式,如果返回的是业务类型列表,应转换为List。   Success方法用来将List转换为前后端约定的标准结果类型Result。 复制代码 /// /// 获取民族可选项列表 /// [HttpGet( "nationItems" )] public IActionResult GetNationItems() { List items = Util.Helpers.Enum.GetItems(); return Success( items ); } 复制代码   再来看TagHelper标签,for属性承包了常规的机械工作,你将注意力集中在业务上,通过手工设置url属性来加载远程数据。   效果跟直接绑定枚举一样,不过生成的Html简单很多。 复制代码 复制代码   上面演示的下拉列表并未分组,我们来改造一下,让它以分组显示。 复制代码 /// /// 获取民族可选项列表 /// [HttpGet( "nationItems" )] public IActionResult GetNationItems() { var result = Util.Helpers.Enum.GetItems() .GroupBy( t => Util.Helpers.String.PinYin( t.Text.Substring( 0,1 ) ) ) .SelectMany( t => t.ToList().Select( item => new Item( item.Text, item.Value, item.SortId, t.Key ) ) ); return Success( result ); } 复制代码   Util包含大量有用的Helper,Util.Helpers.String.PinYin方法能够将汉字转换为拼音首字母缩写,使用GroupBy方法将民族拼音首字母进行分组,并转换为Item标准格式。   执行效果如下。   TagHelper没有任何变化,Angular Material下拉列表是否分组,其原生Html格式完全不同,但封装以后,你根本感觉不到它们的区别,你不需要编写任何一行Ts代码,就完成了分组下拉列表的绑定,你应该已经体会到封装的强大之处。 单选按钮   单选按钮和下拉列表类似,下面演示一下枚举绑定。   C#代码如下。 复制代码 /// /// 性别 /// public enum Gender { /// /// 女 /// [Description( "女士" )] Female = 1, /// /// 男 /// [Description( "先生" )]
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信