My_JIE 发表于 2018-6-11 17:25:41

Ant Design 使用小结

最近公司做了一个系统,因为页面涉及的表单交互非常多,如果使用之前的 Node + Express 的开发模式
效率是非常低的,因此经过考虑,最后决定使用 Node + React 的开发模式,并且使用了蚂蚁金服出品的
开源框架 Ant Design。

正如Ant Design 官方介绍: "在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往
存在很多类似的页面和组件,给设计师和工程师带来很多困扰和重复建设,大大降低了产品的研发效率。
"在这次开发的项目中,因为数据交互非常频繁,大量的表单、时间选择器、表格、单选多选框,以及各
种标识状态的组件,如果光靠手写或者使用 jQuery 插件,其开发的工作量仍然是相当大的,因此,Ant
Design "经过大量的项目实践和总结,沉淀出一个中台设计语言 Ant Design。旨在统一中台项目的前端 U
I 设计,屏蔽不必要的设计差异和实现成本,解放设计和前端的研发资源。"

同时,React 也是第一次使用,起初对于这种 Html 与 JS 代码混杂的书写格式搞得很不舒服,因为作为
一个前端开发者,被"结构与逻辑分离"的思想洗脑太久了,但实际上组件的 HTML 是组成一个组件不可
分割的一部分,能够将 HTML 封装起来才是组件的完全体,React 发明了 JSX 让 JS 支持嵌入 HTML 不得
不说是一种非常聪明的做法,让前端实现真正意义上的组件化成为了可能。所以在使用了 React 开发一
段时间后,越来越感觉到 React 封装组件的高效。而 Ant Design 则封装了一系列高质量的 React 组件,
十分适用于在企业级的应用中,框架提供的 api 十分详尽,上手和使用相对简单,值得一提的是, Ant
Design 使用 ES6 进行编写,因此使用过程中对 ES6 也是一次学习的机会。

在这里主要就项目中使用到的主要的组件以及遇到的一些坑进行总结,也是对这个项目开发的一个简单
的梳理。

项目开发中主要用到的组件:

Button、Icon、Row/Col、BackTop、Pagination、Tabs、Checkbox、Cascader、Form、Rate、Select 、
Modal、Message、Table

Button 的使用比较简单,需要注意的地方是为了和原生 button 进行区分,Form表单中的 Button 组件的
submit 需要写成 htmlType = "submit" ,其他类似的组件也是同理。

举例:



使用了两种类型的Button, 并且定义了 Button 的点击事件,事件在 render() 方法外进行定义。

Icon的使用非常简单,Ant Design 提供了常用了Icon , 使用时只需要点击图标即可完成代码的复制。Ic
on 自带两个属性,type 定义了图标的类型, spin 定义图标是否有旋转动画。

Row / Col 主要用于栅格布局,和BootStrap 的使用十分相似,不同的是 Ant Design 默认把页面分成24
份,而不是12份,增加了布局的灵活度。

Tabs 十分实用,通过Tabs 的切换可以十分高效地在同一个页面中展现不同业务的信息。Tabs 的使用
也十分简单,只要将每一个 Tab 面板放在 Tabpane 标签里面即可。值得一提的是,如果要实现标签的
动态切换(根据 state 的状态进行切换),需要在 Tabs 标签中添加 activeKey 属性,别且绑定 到具体
的控制标签切换的 state 上,更重要的是,需要添加 onChange 方法,在标签切换的事件中设置当前 t
ab 的 state,如下:






Form 是项目中用的最多的表单了,表单项通过 FormItem 进行定义,并且可以设置对其方式、验证规
则、错误提示信息以及绑定初始值,十分有用。






在最外层的 Form 标签上定义了表单的提交方法,并且把所有的表单项都获取复制到 values 参数中,
因此可以对 values 对象进行操作,获取到对象的字段值并经行处理,如下:



Modal 主要在弹框和提示框中使用,如果弹框中需要进行数据操作,如选择和填写信息,则直接使用
Modal ,定义标题,按钮方法,以及弹框中的具体内容;如果只是简单的信息提示,比如成功或失败
、警告等,则可以根据场景使用 Modal 下的 success、warning、confirm、info方法,使用起来也十分
方便,需要注意的是,在这些方法中无法获取到正确的 this 指向,因此需要在方法外部先获取到 this
指针。如下:





Table 的的使用过中则遇到了很多的坑,主要是因为表格中的数据都是通过请求接口获取到的,因此
需要对数据进行动态渲染,同时表格中的数据还允许操作,甚至,有多个表格需要同时渲染,因此
表格的一切都要再请求接口后动态生成,然后渲染到 DOM 结构中去。举例:



在这里表格中的数据通过请求接口获取,每一个 item 包含了表格中的所有数据,同时手动为表格中
的每一行数据添加一个 index 属性用于标识数据的唯一性(所有通过 for 或者 map 循环出来的数据
都需要加标识条目唯一性的 key 值,可以是 id,也可以是自己添加的属性,唯一即可,不这样做的
话浏览器会报出警告)。

当获取到数据并生成所有的表格结构后,将此对象复制给 state,并插入到 DOM,便可以实现动态表
格以及动态数据的插入。表格的使用,谨慎仔细是十分重要的。



Cascader 在封装选择省市区组件中使用到,这里也有较多的技巧。组件代码如下:




My_JIE 发表于 2018-6-11 17:26:43

复制代码
<p>1 import React, { Component, PropTypes } from 'react'</p><p>2 import { connect } from 'react-redux'</p><p>3 import Cascaderfrom 'antd/lib/cascader'</p><p>4 import Buttonfrom 'antd/lib/button'</p><p>5 import message from 'antd/lib/message'</p><p>6 import ajax from '../../utils/service'</p><p>7 import * as inputActions from '../../actions/input'</p><p>8 </p><p>9 let province = [</p><p> 10   { value: 2, label: "北京", isLeaf: false },</p><p> 11   { value: 3, label: "安徽", isLeaf: false },</p><p> 12   { value: 4, label: "福建", isLeaf: false },</p><p> 13   { value: 5, label: "甘肃", isLeaf: false },</p><p> 14   { value: 6, label: "广东", isLeaf: false },</p><p> 15   { value: 7, label: "广西", isLeaf: false },</p><p> 16   { value: 8, label: "贵州", isLeaf: false },</p><p> 17   { value: 9, label: "海南", isLeaf: false },</p><p> 18   { value: 10, label: "河北", isLeaf: false },</p><p> 19   { value: 11, label: "河南", isLeaf: false },</p><p> 20   { value: 12, label: "黑龙江", isLeaf: false },</p><p> 21   { value: 13, label: "湖北", isLeaf: false },</p><p> 22   { value: 14, label: "湖南", isLeaf: false },</p><p> 23   { value: 15, label: "吉林", isLeaf: false },</p><p> 24   { value: 16, label: "江苏", isLeaf: false },</p><p> 25   { value: 17, label: "江西", isLeaf: false },</p><p> 26   { value: 18, label: "辽宁", isLeaf: false },</p><p> 27   { value: 19, label: "内蒙古", isLeaf: false },</p><p> 28   { value: 20, label: "宁夏", isLeaf: false },</p><p> 29   { value: 21, label: "青海", isLeaf: false },</p><p> 30   { value: 22, label: "山东", isLeaf: false },</p><p> 31   { value: 23, label: "山西", isLeaf: false },</p><p> 32   { value: 24, label: "陕西", isLeaf: false },</p><p> 33   { value: 25, label: "上海", isLeaf: false },</p><p> 34   { value: 26, label: "四川", isLeaf: false },</p><p> 35   { value: 27, label: "天津", isLeaf: false },</p><p> 36   { value: 28, label: "西藏", isLeaf: false },</p><p> 37   { value: 29, label: "新疆", isLeaf: false },</p><p> 38   { value: 30, label: "云南", isLeaf: false },</p><p> 39   { value: 31, label: "浙江", isLeaf: false },</p><p> 40   { value: 32, label: "重庆", isLeaf: false },</p><p> 41   { value: 33, label: "香港", isLeaf: false },</p><p> 42   { value: 34, label: "澳门", isLeaf: false },</p><p> 43   { value: 35, label: "台湾", isLeaf: false }</p><p> 44 ];</p><p> 45 </p><p> 46 let options = province</p><p> 47 </p><p> 48 class Location extends Component {</p><p> 49   constructor(props) {</p><p> 50         super(props);</p><p> 51 </p><p> 52   }</p><p> 53   state = {</p><p> 54         options: options,</p><p> 55         inputValue: '',</p><p> 56   }</p><p> 57 </p><p> 58   componentWillMount(){</p><p> 59         const {hideLoading} = this.props;</p><p> 60         setTimeout(() => { hideLoading()}, 1000)</p><p> 61   }</p><p> 62 </p><p> 63   _onChange = (value, selectedOptions) => {</p><p> 64         console.log(value);</p><p> 65         this.setState({</p><p> 66             inputValue: selectedOptions.map(o=>o.label).join(', ')</p><p> 67         });</p><p> 68         const onChange = this.props.onChange;</p><p> 69 </p><p> 70         if (onChange) {</p><p> 71             onChange({...value});</p><p> 72         }</p><p> 73   }</p><p> 74 </p><p> 75   _loadData = (selectedOptions) => {</p><p> 76         const targetOption = selectedOptions;</p><p> 77         const id = targetOption.value;</p><p> 78         targetOption.loading = true;</p><p> 79 </p><p> 80         if (selectedOptions.length == '1') {</p><p> 81             // 点击省,获取市</p><p> 82             ajax.post(ajax.api.GETSERVICEAREA, { id: id, token: localStorage.token }).then(data => {</p><p> 83               if(data.status.code == '1'){</p><p> 84                     targetOption.loading = false;</p><p> 85                     targetOption.children = [];</p><p> 86                     data.result.map((v, k)=>{</p><p> 87                         // 拼出市</p><p> 88                         targetOption.children.push({ </p><p> 89                           value: v.id,</p><p> 90                           label: v.name,</p><p> 91                           isLeaf: false, </p><p> 92                         });</p><p> 93                     });                </p><p> 94                     this.setState({</p><p> 95                           options: [...this.state.options],</p><p> 96                     });</p><p> 97               }</p><p> 98             });</p><p> 99         }else if (selectedOptions.length == '2') {</p><p>100             // 点击市,获取区</p><p>101             ajax.post(ajax.api.GETSERVICEAREA, { id: id, token: localStorage.token }).then(data => {</p><p>102               if(data.status.code == '1'){</p><p>103                     targetOption.loading = false;</p><p>104                     targetOption.children = [];</p><p>105                     data.result.map((v, k)=>{</p><p>106                         // 拼出区</p><p>107                         targetOption.children.push({ </p><p>108                           value: v.id,</p><p>109                           label: v.name,</p><p>110                           isLeaf: true, </p><p>111                         });</p><p>112                     });                </p><p>113                     this.setState({</p><p>114                           options: [...this.state.options],</p><p>115                     });</p><p>116               }</p><p>117             });</p><p>118         }else { </p><p>119             targetOption.loading = false;</p><p>120         }</p><p>121   }</p><p>122 </p><p>123   _resetLocation = () => {</p><p>124         document.getElementsByClassName('ant-cascader-picker-clear').click();</p><p>125         this.setState({</p><p>126             inputValue: '',</p><p>127         });</p><p>128   }</p><p>129 </p><p>130   render() {</p><p>131         return (</p><p>132             <div></p><p>133               <Cascader</p><p>134                     options={this.state.options}</p><p>135                     loadData={this._loadData}</p><p>136                     onChange={this._onChange}</p><p>137                     changeOnSelect</p><p>138                     placeholder=""</p><p>139               /></p><p>140             </div></p><p>141         )</p><p>142   }</p><p>143 }</p><p>144 </p><p>145 export default connect(state => ({</p><p>146   showLoginLayer: state.login.isShowLoginLayer</p><p>147 }), inputActions)(Location)</p>
复制代码
其中要注意的地方很多,首先是初始值的设置。因为使用省市区组件,不能一次性拉取到所有的地区,
这样会十分消耗性能,造成不良的用户体验,正确的做法是在点击时获取下一级的地区信息,这样有针
对性的请求可以减少加载时间,那么问题来了,如何实现?

首先,使用 Cascader 的 api 中有 loadData 属性,可以定义数据的加载,同时,onChange 事件可以监
听到每一次的数据变化,但是使用 Cascader 有一个限制,便是第一级的数据需要在加载组件之前就定
义并获取到,否则无法进行下一级数据的加载,因此这里单独定义了所有了省的数据:





onChange 事件获取到当前选择的对象,并提供了两个参数,分别是选择的当前值(数组类型)和选择
的多级对象(当前选择的对象和当前对象下一级的数组对象)。

在获取到当前选中的对象后对值进行拼接处理并赋值给 inputValue (在组件框中显示的选中值),同时
设置onChange()方法,将值的变化情况通知给父组件(如 FormItem )



loadData()方法用于组件级联数据的加载,其参数就是 onChange()方法的第二个参数,在这里获
取到参数的最后一个对象(即当前点击的对象),通过判断参数的长度来识别当前要获取的数据的类型
(市还是区),获取到数据后生成组件要求的数据格式,最后将格式化的数据赋值给 option 就可以了。





最后,模拟了一个重置(清空)的功能,Cascader 值的清空有两点,一是选择框内容的清空,二是级
联数据的重置,内容的清空比较简单,直接将 inputValue 设置为空即可,而级联数据的重置不能将 o
ption 设置为空,这样会导致级联组件无法使用(下拉数据为空),为此使用了一个小技巧:

在组件的 api 属性中有一个allowClear 属性,默认是 true,即允许清空,其效果是当鼠标移到选择数据
后的级联表单上会出现一个 叉号的按钮,如下:



通过开发者工具可以获取到这个按钮的类名,那么之后的操作就简单了: 自定义一个重置(清空)按钮,
然后触发清空按钮的事件即可:




页: [1]
查看完整版本: Ant Design 使用小结