内容简介:前端开发过程中,往往会遇到很多的表单。简单表单尚可,但复杂表单让人尤为头疼。比如有一个用来提交第一个表单项是 Radio,为性别:分别是
一、背景
前端开发过程中,往往会遇到很多的表单。简单表单尚可,但复杂表单让人尤为头疼。
比如有一个用来提交 请假单申请
的表单。
第一个表单项是 Radio,为性别:分别是 男
和 女
两种选项。
第二个表单项是个 Select,为请假原因:分别为 事假
、 年假
、 调休
、 病假
这四种选项。另外,当上一个性别选项,选择为 女
后,则额外增加一个 产假
的选项。
第三个表单项是个 Uploader,为图片上传组件,用于上传医院证明:该项仅在请假原因选择为 病假
或 产假
后显示,其余情况不显示。
这个场景已经略微有点复杂度了。很多时候如果不好好设计,将写出不好维护的代码。
那如果更加复杂的场景呢?
有赞云业务中,这种场景非常常见,于是推出了 zan-form
来解决这个问题。
二、配置式
对于上面提到的场景,先思考下,用普通方式怎么写。用普通的方式写一遍后,会发现有点难受。为什么呢?
因为 jsx
,本质上是现实中的物理模型,适合像积木一样,去拼凑出各种各样的 UI。
它一旦加上复杂的判断逻辑后,就会很杂乱,需要花大量精力去整理和修饰。
再想一下 配置式
,第一直觉是什么?是逻辑和规则。
表单跟一般的 UI 不一样,尤其复杂的表单,它特别重逻辑,但是视觉复杂度上并不高,不会存在特别多层级的嵌套。所以用配置式来写复杂表单,也许是一个更好的方案。
下面是用配置式表单解决上面提到的场景:
[ { _component: "FormRadioGroupField", _name: "sex", data: [ { text: "男", value: "male" }, { text: "女", value: "female" } ] }, { _component: "FormSelectField", _name: "qingjiaType", data: [ { text: "事假", value: "shijia" }, { text: "年假", value: "nianjia" }, { text: "调休", value: "tiaoxiu" }, { text: "病假", value: "bingjia" }, { text: "产假", value: "chanjia" } ] }, { _component: "ImageUploader", _name: "hospitalMaterial", _show: values => ["bingjia", "chanjia"].includes(values.qingjiaType), tokenUrl: "http://somecdn.youzan.com" } ];
在上面的配置代码中, _component
和 _name
就不多说了,解释下 _show
方法。
_show
目前仅支持传入一个同步函数,其中入参为 values
。
vlaues
等于 zentForm.getFormValues()
所取得的值。
_show
的出参为一个布尔值,当 true
时,表示该组件显示;当为 false
时,表示该组件不显示。
总体来说,就是把一份配置文件,做为一个数组进行遍历。当遇到 _show
方法时,执行之,并根据其 返回值,决定是实例化的这个组件,还是直接返回 null
。
三、禁用 children
配置式组件,是否应该支持 children
,这是一个有争议性的问题。
支持后有利有弊,但最终还是决定禁止 children
的使用。
原因是,一旦支持实现 children
后,整个配置就偏视觉,而非偏逻辑了。
所以,以下方式是不被允许的:
// 下面的写法,会报错。因为不被允许使用 children { _component: 'FormRadioGroupField', _name: 'sex', children: [ { _component: 'Radio', value: 'male', text: '男' }, { _component: 'Radio', value: 'female', text: '女' } ] },
当然,为了方便使用,已经对 zent
自带的 FormCheckboxGroupField
和 FormRadioGroupField
这两个组件进行了封装。封装后用法跟 FormSelectField
类似,仅需要传入 [{ text: 'text', value: 'value' }]
即可。如下所示:
// 封装后的 FormRadioGroupField 用法 { _component: "FormRadioGroupField", _name: "sex", data: [ { text: "男", value: "male" }, { text: "女", value: "female" } ] },
所以对自己写的表单组件,如果需要用到 children
属性,会遇到一些问题,需要做一些改造。
当然,也有其他方式可以绕过,就是后面小节会提到的 _slot
。
四、注册自定义表单组件
到目前为止,能够直接在配置文件中使用的组件,都是 zent.Form
下的组件。
那么对于自定义组件怎么处理呢?
当前在 zan-form
内部维护了一个 componentLib
对象。
在该对象中, key
是组件名, value
是 Component。当解析配置文件的时候,会根据 _component
字段,去找到对应的组件,并实例化它。
所以我们只需要想办法,在 componentLib
中放入我们自定义的组件就可以了。
zan-form
提供了 zanForm.register('ComponentName', MyComponent)
方法,可以在 componentLib
中注册我们自己的组件。
注意,第三节也提到过,如果自定义组件需要用到 children
属性,需要对该组件进行改造,因为当前的配置式是不支持 children
的。
为了能在 zentForm 中更好地使用,自定义组件需要暴露 value
属性,以及支持 onChange
方法做为回调,并用 zent.Form.Filed
包裹。
五、插槽
某些场景下,纯粹的配置式无法满足需求,则需要用 插槽
来实现扩展了。
插槽
的使用如下:
// form.config.js [ { _slot: "ImageUploader" }, { _slot: "MyFooter" } ]; // Form.jsx import zanForm from "zan-form"; import formConfig from "./form.config.js"; const Slot = zanForm.Slot; class Form extends Component { render = () => { return zanForm(formConfig, this)( <React.Fragment> <Slot id="ImageUploader"> <ImageUploader>点击上传图片</ImageUploader> </Slot> <Slot id="MyFooter"> <Footer>我是页脚</Footer> </Slot> </React.Fragment> ); }; }
如何使用 插槽
,总结起来就是两个步骤:
首先,在配置中,选择合适的点,预留一个插槽。
然后,在配置外的 zanForm
中,使用 Slot
定义一个 待插入组件
,并赋予唯一 id。
注意, _slot
可以跟 _show
一起使用,但是其他的,例如 _fetch_data
、 _format
等,一律不支持。
再说下 _slot
的实现原理,其实也很简单:
在 zan-form
内部维护有一个 slotMap
对象,以 Slot.id
做为 key
, Slot.children
做为 value
。当遍历配置文件时,遇到 _slot
,则去 slotMap
里面取对应的 Slot.children
,并填充进插槽即可。
六、与服务端的交互
在日常开发中,存在着大量与服务端交互的场景。
比如 FormSelectField
中的 data
,需要从服务端获取。
一般的方式是,在 componentDidMount
中获取数据,并且通过 setState
塞入到 FormSelectField
的 data
中去。
然而在配置文件中,这似乎很难实现。那么在配置式中,怎么样获取远程数据呢?
目前可以通过 _fetch_data
方法来返回一个 Promise
的方式来实现,如下所示:
// "店铺类型"的Select { _component: 'FormSelectField', _name: 'shopType', _fetch_data: () => { return getShopType().then(items => { return items.map(item => { return { text: item.desc, value: item.type, }; }); }); }, data: [], },
以下两点值得注意:
1、原组件(如 FormSelectField 组件)必须支持 data
属性。因为获取到的数据,会默认塞入到 data
中去。
2、 _fetch_data
只会触发一次。
实现方式:
这个特性,目前是通过在【原组件】外面,又另外包裹了一层 DecoratorCoponent
实现的。
当 DecoratorComponent
触发 componentDidMount
的时候,就去调用 _fetch_data
,并将 data
通过 props
传给【原组件】。
也就解释了上面提到的,为什么 _fetch_data
只会触发一次。
七、重启组件
存在一种场景,如下:
第一行表单项,是一个 FormSelectField
,代表省份。
第二行表单项,也是一个 FormSelectField
,代表城市。但是该项会根据省份 id,动态从服务端获取城市数据。也就是说,上一项省份改变时,该项城市列表也会改变。
根据上述第六节提到的,如果只用 _fetch_data
,那么根据省份 id 获取城市列表数据,只会触发一次,不会再触发第二次。
所以提出了一个 重启
组件的概念。
重启组件的内部实现,实际上分为两个步骤:
第一步,在 DecoratorCoponent
中改变【原组件】的 key
来重启原组件。
第二步,重新触发自身的 _fetch_data
函数,重新将 data
通过 props
传给【原组件】。
如下所示:
[ { _component: "FormSelectField", _name: "province", required: "请选择您所在省份", data: [{ text: "浙江省", value: 30 }, { text: "广东省", value: 31 }] }, { _component: "FormSelectField", _name: "city", _fetch_data: values => { if (!values.province) { return Promise.resolve([]); } else { return fetchCityByProvince(values.province); } }, _subscribe: (prevValues, values, restart) => { if (values.province !== prevValues.province) { restart(); } }, required: "请选择您所在城市", data: [] } ];
欲 重启
,需先 订阅
。使用 _subscribe
可订阅。
_subscribe
方法中接受三个入参,分别是上一个状态的 values
,本状态的 values
,以及一个用来重启组件的 restart
方法。
比较两次状态的 values.province
,如果不同,则触发重启。
至于 _subscribe
,其实是借助 DecoratorCoponent
中的 componentDidUpdate
实现的。
八、格式化组件
有时候,需要对组件做一些样式上的修改,那么在配置式中,怎么做呢?
目前提供了一个 _format
方法来实现这一点。
如下所示:
[ { _component: "FormInputField", _name: "age", _format: ($component, values) => ( <div> {$component} <span>岁</span> </div> ), label: "年龄" } ];
format
方法中,\$component 是组件实例,values 同 _show
方法的 values
。它们做为参数传入,最后返回一个改动后的组件实例。
_format
方法的内部实现,跟 _show
类似。
九、表单回填
一些场景下,比如编辑的时候:需要当从服务端取回数据,并在表单上回显。
这种场景该怎么做呢?
zan-form
提供了一个 zanForm.setValues(values, this)
方法,即可把数据回填回去。
本质上, setValues
方法,是对 zentForm.setFieldsValue()
的一个递归调用,最终使 zentForm.getFormValues()
的值趋向于稳定。
十、有赞云招人
有赞云大量 hc 招聘前端,有兴趣的可以加微信 socialHunter
咨询!
欢迎关注我们的公众号
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 表单 – 避免Symfony强制显示表单字段
- 细说 Angular 2+ 的表单(二):响应式表单
- 8款最新CSS3表单 环形表单很酷
- 动态表单 form-create 2.5 版本来啦,帮你轻松搞定表单
- 开源 | vue-form-making:基于 Vue 的表单设计器,让表单开发简单而高效
- 表单验证(AngularJs)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ruby语言入门
Yugui / 丁明、吕嘉 / 东南大学出版社 / 2010 年4月 / 32.00元
《Ruby 语言入门(中文版)》为具有一定其他语言的编程经验的读者介绍Ruby的特征、Ruby中的编程方法和编程习惯。这些内容都是为了让读者能够边阅读Ruby的资料边进行实践性的学习所必须具备的基础知识。《Ruby 语言入门(中文版)》对Ruby的基础部分和元类、块语句这样独特的概念,以及由此产生的特有的文化进行了说明,以使读者能够了解到Ruby独特的思考方式。读完《Ruby 语言入门(中文版)》......一起来看看 《Ruby语言入门》 这本书的介绍吧!