想象一下,该表格允许您在通过中介机构-签证中心处理申根签证的同时获取新样品的外国护照。这个例子似乎官僚主义足以证明我们的复杂性。
因此,在我们的项目中,我们面临着许多具有某些属性的块的形式:
- 在这些字段中有输入框,多项选择,自动完成字段。
- 这些块链接在一起。假设您需要在一个区域中指定内部护照的数据,而在下面将有一个区域,其中包含签证申请人的数据。同时,还与签证中心签有内部护照协议。
- – , , ( 10 , ) .
- , , . , 10- , . : .
- . . .
最终形式在垂直方向上占据约6000像素-总共约3-4个屏幕,总共80多个不同的场。与这种形式相比,国家服务部的申请似乎并不那么好。就大量问题而言,最接近的事情可能是针对某大型公司的安全服务调查表,或者是有关视频内容偏好的无聊民意调查。
在实际问题中,大型表格并不常见。如果我们尝试“正面”实现这种形式(类似于习惯于使用小型表单的方式),那么结果将无法使用。
主要问题是,当您在适当的字段中输入每个字母时,将重新绘制整个表格,这会带来性能问题,尤其是在移动设备上。
而且,不仅对于最终用户,而且对于必须维护该表单的开发人员来说,都很难处理该表单。如果不采取特殊步骤,则代码中的字段之间的关系将很难跟踪-一处更改会带来有时难以预测的后果。
我们如何部署最终形式
该项目使用React和TypeScript(完成任务后,我们完全切换到TypeScript)。因此,为了实现表单,我们从Redux Form的创建者那里获取了React Final-form库。
在项目开始时,我们将表单分为几个单独的块,并使用了Final-form文档中描述的方法。las,这导致了一个事实,一个字段中的输入对整个大格式都进行了更改。由于图书馆是相对较新的,因此那里的文档仍然很年轻。它没有描述改善大型模具性能的最佳方法。据我了解,在项目中很少有人遇到这个问题。对于小型表单,该组件的一些额外重绘不会影响性能。
依存关系
我们首先要面对的晦涩之处是如何准确实现字段之间的依赖关系。如果严格按照文档进行操作,则由于大量相互关联的字段,表格的增长速度会开始放慢。关键是依赖关系。该文档建议在该字段旁边放置对外部字段的订阅。这就是我们项目的方式-负责连接字段的适应版本的react-final-formlisteners与组件位于同一位置,也就是说,它们位于各个角落。依赖关系很难找到。这使代码量大增-组件巨大。一切都缓慢进行。为了更改表单中的某些内容,您必须花费大量时间在所有项目文件中进行搜索(项目中大约有600个文件,其中有100多个是组件)。
我们已经做了几次尝试来改善这种情况。
我们必须实现自己的选择器,该选择器仅选择特定块所需的数据。
<Form onSubmit={this.handleSubmit} initialValues={initialValues}>
{({values, error, ...other}) => (
<>
<Block1 data={selectDataForBlock1(values)}/>
<Block2 data={selectDataForBlock2(values)}/>
...
<BlockN data={selectDataForBlockN(values)}/>
</>
)}
</Form>
可以想象,我不得不提出自己的建议
memoize pick([field1, field2,...fieldn])
。
所有这些结合在一起
PureComponent (React.memo, reselect)
导致了这样一个事实,即仅在块所依赖的数据发生更改时才重绘块(是的,我们将Reselect库引入了之前未使用的项目中,借助它,我们可以执行几乎所有数据请求)。
结果,我们切换到一个侦听器,该侦听器描述了表单的所有依赖关系。我们从final-form-calculate项目(https://github.com/final-form/final-form-calculate)中采用了这种方法的想法,并将其添加到我们的需求中。
<Form
onSubmit={this.handleSubmit}
initialValues={initialValues}
decorators={[withContextListenerDecorator]}
>
export const listenerDecorator = (context: IContext) =>
createDecorator(
...block1FieldListeners(context),
...block2FieldListeners(context),
...
);
export const block1FieldListeners = (context: any): IListener[] => [
{
field: 'block1Field',
updates: (value: string, name: string) => {
// block1Field ...
return {
block2Field1: block2Field1NewValue,
block2Field2: block2Field2NewValue,
};
},
},
];
结果,我们得到了字段之间所需的依赖关系。另外,数据存储在一个地方,使用起来更加透明。此外,我们知道订阅以什么顺序触发,因为这也很重要。
验证方式
通过类比依赖,我们已经进行了验证。
在几乎每个领域中,我们都需要检查人员输入的年龄是否正确(例如,文档集是否符合指定的年龄)。从散布在所有表格中的数十种不同的验证器中,我们切换到一个全局验证器,将其分解为单独的块:
- 护照数据验证器,
- 行程数据验证器,
- 有关以前签发的签证的数据,
- 等等
这几乎没有影响性能,但是加速了进一步的开发。现在,进行更改时,您无需遍历整个文件即可了解各个验证器中发生的情况。
代码重用
我们从一个大的形式开始,在这个大形式上我们运行我们的想法,但是随着时间的推移,该项目不断发展-出现了另一个形式。自然,在第二种形式中,我们使用了所有相同的想法,甚至重用了代码。
以前,我们已经将所有逻辑移到了单独的模块中,那么为什么不将它们连接到新表单呢?这样,我们大大减少了代码量和开发速度。
同样,新表单现在具有与旧表单相同的类型,常量和组件-例如,它们具有一般授权。
代替总数
问题是合乎逻辑的:为什么我们不使用另一个库来存储表单,因为这个库很困难。但是无论如何,大型表格都会有自己的问题。过去,我本人曾与Formik合作。考虑到我们确实找到了问题的解决方案,因此最终表单更加方便。
总体而言,这是使用表单的绝佳工具。连同代码库开发的一些规则,他帮助我们大大优化了开发。所有这些工作的额外好处是能够使新的团队成员更快地更新。
突出显示逻辑之后,就可以更清楚地了解特定字段所依赖的内容-不必为此而阅读三层需求。在这种情况下,审核错误现在至少需要两个小时,尽管可能需要几天才能完成所有这些改进。一直以来,开发人员一直在寻找幻像错误,但根据其自身的表现尚不清楚。
本文的作者:Oleg Troshagin,Maxilekt。
PS:我们在Runet上的多个站点上发表了文章。订阅我们在VK,FB,Instagram或Telegram频道上的页面,以了解我们的所有出版物以及Maxilect的其他新闻。