10分钟精通Ant Design Form表单

  1. 被人诟病的Form
  2. Form的原理
  3. Vue版Form的进化史

本文适合React、Vue开发者阅读,10分钟不够?那就再加10分钟。javascript

被人诟病的Form

antd被人吐槽最多的除了彩蛋以外,那应该就是Form表单了。若是须要使用Form自带的收集校验功能,须要使用Form.create()包装组件,每个须要收集的值还须要getFieldDecorator进行注册。官方文档大量的让人眼花缭乱的API,大几率没有多少人读完了整个文档,即使读完了,大几率也记不住。html

写这篇文章不是为了吐槽Form表单,固然我也并无更好的优化Form表单的方案,本文的目的是但愿你们可以经过本文了解Form表单的本质,更好的使用的Form表单。vue

Form的原理

网络上有一些源码分析的文章,我的以为收益比不高,逐条过api式的讲解甚是无趣。 一句废话归纳原理:Form.create建立一个具备注册、收集、校验功能的"实例"。java

咱们把这句话分红几个关键词逐一分析:Form.create建立实例、注册、收集、校验 四个关键词git

Form.create建立"实例":

实例?为何不是组件。Form.create的核心能力是建立实例this.props.form,并非建立组件。 这个实例提供一系列的方法,如注册、收集、校验github

那为何要包装组件呢?包装组件的目的是为了更新组件,仅此而已。api

你应该知道全部须要该实例帮助你进行收集校验的组件,必需要经过getFieldDecorator进行修饰,一旦通过getFieldDecorator的修饰,那么该组件的值将彻底由该实例管理。组件的更新须要组件所在上下文处执行render, 咱们知道组件的更新有两种方式:1. 父组件更新了 2. 自身状态改变了微信

因此进一步讲,包装组件的目的就是为了被包装组件的父组件更新,一旦被getFieldDecorator修饰过的组件触发onChange事件,便会触发这个父组件的的更新(forceUpdate),从而促使被包装组件的render。 如:Form.create()(A) A就是咱们所说的被包装组件网络

注册(getFieldDecorator):

getFieldDecorator的目的是为了把须要收集的数据在实例中进行注册,并把注册的值同步到被getFieldDecorator修饰的组件B上。因此组件B不可以在经过value赋值,组件B的状态将所有由getFieldDecorator托管。antd

收集、校验

收集校验就更简单了,你能够认为收集校验就是这个实例提供的几个方法而已。

丢弃Form.create

若是Form.create的核心能力是建立"实例",是否是意味着能够不用Form.create包裹组件呢? 答:是的,若是把更新组件的副能力解决掉。刚好Ant Design Vue就是这么去作的。

Vue版Form的进化史

起初咱们使用了和React版一致的写法,必须使用Form.create包裹组件,但vue推崇的template语法很难再去使用,你不得不去在Vue中使用JSX,遭到了用户的各类吐槽。 而后咱们进行了改版,将Form.create放在了Form中去执行,经过回调的方式将Form.create建立的示例传递回来:

<a-form :autoFormCreate="form => this.form = form">...</a-form>
复制代码

注册经过a-form-item添加对应属性来劫持子元素进行注册。

<a-form-item fieldDecoratorId="name" :fieldDecoratorOptions="{ rules: [{ required: true, message: 'xxxx !' }]  }" >
  <a-input/>
</a-form-item>
复制代码

这样一种设计他有很大的问题:

  1. form不能及时拿到,咱们应该在组件render以前拿到form实例
  2. 经过a-form-item劫持子元素有很大的限制,每个a-form-item下只能注册一个,固然这个问题不大,咱们能够在提供一个a-form-control专门用来注册组件,O__O "…嵌套好深。

最终方案:

实例:

既然Form.create的主要能力是建立"实例",咱们能够暂时抛开组件,先解决构建实例的问题,

createForm(options = {}) {
  return new Vue(Form.create(options));
}
复制代码

咱们在组件上提供一个静态方法createForm来建立这个示例,那么有了这个和组件没有任何关系的方法,就能够随时建立"实例",同一个组件中也能够同时拥有多个"实例"。核心能力有了,但没有副能力也是不行的,就像没有了四肢的大脑,有心无力。 前面讲了,组件的更新须要组件所在上下文处执行render,那么问题就简单了,咱们只须要把当前组件的上下文传递给这个"实例",当注册到实例的组件须要更新时,直接调用context.$forceUpdate()便可。代码以下:

createForm(context, options = {}) {
  return new Vue(Form.create({ ...options, templateContext: context })());
}
复制代码
注册:

直接新增一个组件a-form-control专门用来劫持组件并注册是一个不错的选择,可是我不想让组件嵌套太深,因此咱们仍是使用a-form-item进行劫持组件,为了可以区分须要劫持的哪些组件,咱们使用指令进行标记并传值, 之因此使用指令是由于咱们不该该为一个须要注册的组件传递一个不相关的属性,若是传递一个未经声明的属性,则该属性会被挂载到dom上,若是要声明属性,就必须对自定义表单控件添加额外约束。而使用指令进行标记和传值不会存在这类问题。

<a-input v-decorator="[ 'note', {rules: [{ required: true, message: 'Please input your note!' }]} ]" />
复制代码

校验收集和React版没有区别,都只是"实例"的方法。

为何不支持双向绑定

严格来讲并非彻底不支持,若是你不须要Form的自动收集、校验功能,是可使用双向绑定的。 双向绑定在某些业务场景下的确能够节省不少代码,但对于某些状况下又给咱们带来了没必要要的麻烦。 举一个很简单也很常见的栗子: 在系统中同一份数据被多处组件(包含可编辑的Form)使用是常有的事情,咱们在表单中改变这份数据,同时数据的改变同步到各个相关组件中,很是easy的完成了需求。但不少时候咱们但愿表单数据改变后并不须要及时的同步到其它组件中,而是当用户点击肯定按钮后才将数据同步,咱们就不得不将这份数据进行复制甚至是深复制来知足需求,甚是蛋疼。

而若是使用ant-design-vue单项数据流的方式,数据之间的流向就变得很是清晰,表单就像一个独立的沙盒,无论沙盒中的数据如何变化,都不会影响到沙盒的外部,而沙盒经过相关API方法和外部进行交互。

最后,10分钟精(wo)通(shi)不(biao)存(ti)在(dang)的,但但愿你们可以经过本文对antd的Form有一个进一步的认知,Form依然还有不少的功能须要你们本身去探索,在这就不一一展开了,我想也没有必要展开。若是你们有更好的方案也欢迎提issue提pr,一块儿探讨,将ant-design-vue打形成世界第二好用的Vue UI组件库。 谁是第一好用的?你问我? 那固然也是ant-design-vue,且不接受任何异议,就是那么自信,那么臭不要脸。

最后的最后,给团队微信公众号打个广告,微信搜索“一点大数据技术团队”关注公众号,你没看错,就是大数据,若是你对大数据感兴趣,欢迎关注该公众号,咱们每个月会从团队内部筛选出两篇左右的高质量原创文章。