你曾经做过“动态的表单”么?

755 阅读4分钟

让我们首先思考一下这个问题:“现在又一个动态表单的模块,我应该怎么做?

ok

什么是表单?表单即是指供用户选择、填写等操作最后提交答案的一份问卷。

什么是动态表单?顾名思义,就是通过目前用户已经回答出的答案进行动态的展示符合当前答案的情况下,该出现(或隐藏)的题目。称之为“动态”。

那么应该怎么去做一个动态表单呢?

在不脱离业务场景的情况下,不出意外,绝大多数此类表单都是应该和后端同学一起协商的:“我点这个选项,调用个接口,你返回当前该出现的题目”。

这样的做法很常见,也很简单,对于前端来说,只需要每次传参数调接口,对于后端来说,只需要维护一份映射即可,根据参数返回对应题目的id然后查库就行。你好我好大家好,何乐而不为?可是大家都知道这种方式的最大弊端,就是频繁的调用接口,占用服务器资源,再遇到个恶搞的用户,频繁切换选项,你服务器还要不要了,是不是要爆炸了?

故,否之。

恰巧,最近刚完成了一个动态表单,很乐意去分享我的思路。

业务场景: 用户是通过 “单选/多选”,选中其中某 “一个/几个” 选项,会 “出现/隐藏” 另外 “一道/几道” 问题(输入题,选择题,选择时间,选择数字,上传图片等)。后端返回时会把所有问题全部返回,根据某个字段的值,去判断对应的题的显示或者隐藏(比如: 第一题选a,那么第三题就出现(或隐藏),再根据第三题的show字段判断到底是显示还是隐藏)

(以上我加了引号方便读者区分条件)

你现在是否已经有了思路?那么我就要开始叨叨啦。

首先,这么多种题,肯定是要有这么多种控件的,radio,checkbox,timePicker 等等,十几种控件,那我就建立了十几个这样的.vue文件,作为最底层。

然后,每个表单相当于一个容器,承载着每道题所以再刚才的底层组件的上层,应该是这个表单容器,在表单容器里面请求数据,分发到每个子控件里,组装好答案返回给父容器,嗯完美!

wait a minute ... 表单校验在哪层实现?对各个控件的统一处理在哪里实现?难道要再每个具体的组件里面完成?那也太多重复的劳动吧,且不说能不能做,就是做完后每个组件都会显得异常笨重。

所以在表单容器层和子控件层再添加一层吧。这一层的作用,就是把数据传递给每个子组件,并把子组件提交的答案进行统一的处理,以及最最最重要的表单校验。

好,整体的思路有了,形成图形大概就是下图所示:

梳理:

  1. 表单容器: <1>请求数据,把数据分发到下一层 <2>接收答案,提交给接口
  2. 统一处理: <1>接收上层传下来的数据,以及校验规则,将数据传给对应的子控件 <2>接收答案,校验答案
  3. 子组件: <1>接收数据,如果已经提交过答案需要回显 <2>提交答案

Good~

整体结构和提交的答案有了,下一步面临的问题就是“如何显示隐藏子问题?”

首先思考一下场景: 

1.假设不考虑回显答案的情况(该表单还未被提交过),用户在选择一个单选答案时,要遍历所有问题,找到所有符合控制规则的问题,将其显示/隐藏

2.如果该表单已经被提交过,那么就涉及一个答案回显的过程。<1> 如果你选的是单选题,那需要找到所有该题控制的子问题,并且看子问题有没有被提交过,如果子问题被提交过就要找子问题的子问题,无疑是个递归方法。 <2> 如果你选择的多选题的好几个选项,那就要分别根据选项控制的对应题目去递归,整体思路和<1>相仿。

这里有一个注意点: 在使用BFS或DFS时,要对已经遍历过的子树加标志,防止重复遍历。

整体思路和结构就是这样啦,可能是由于我已经实现了所以感觉没有那么难,但是当初在刚接到这个需求的时候确实是有一定难度的。

现在业务功能已经实现了,我在想如何逐步抽象出来变成通用的组件,但是目前的问题是依赖的字段太多(多是接口里的字段),这些定制化的字段如何抽象起来是目前最大的难点。

欢迎大家给我留言idea或指出不足,我们可以进一步讨论哦~