专栏首页进击的全栈表单联动解决方案探讨
原创

表单联动解决方案探讨

导语 身为一个前端开发,相信诸如“如果这一项选择了A,那一项就只能选B”的需求,大家一定遇到过。“表单联动”是前端开发中的经典场景。本文对于表单联动问题的解决方案进行一个简单的探讨。

表单依赖关系的建模

表单联动是前端经常面临的问题,联动实际上是一组表单项和表单项之间的依赖关系的集合。比如经典的“省-市-区”三级联动,就包含了“省”、“市”、“区”三个表单项,以及“市->省”和“区->市”。表单项的依赖关系可以抽象成若干if(省 === '广东省') { 市 in [‘广州市’, ‘深圳市’, ...] }的形式,即“市”依赖于“省”。(被依赖项也可以有多个,比如C依赖于B和A;而依赖项有多个的情况可以拆解为相互独立的依赖关系)

基于这样的关系,表单项之间的依赖关系其实就可以用有向图来解释。表单项是图中的各个节点,而依赖关系就是图中的有向弧。以经典的“省-市-区”关系来举例,建立如下的图模型:

省市区依赖关系

可以看到,“省-市-区”的依赖关系是一个相对简单的模式,可以用“依赖链”来表示。根据依赖关系的复杂程度,“依赖图”可以简化成“依赖树”或者“依赖链”。

省市区-学校模型

对于简单的依赖关系,只需要简单地监听被依赖项的变化,再更新依赖项的范围即可。比如(React DEMO):

<Select value={province} options={PROVINCE_OPTIONS} onChange={(v) => setProvice(v)} />
<Select value={city} options={getCityOptions(province)} onChange={(v) => setCity(v)} />
<Select value={district} options={getDistrictOptions(city)} onChange={(v) => setDistrict(v)} />

而当依赖关系变得更加复杂时,这种分布式管理的方式,会让依赖关系变得难以维护。比如下方的略显复杂依赖模型:

套餐包购买配置

这一类比较复杂的依赖关系,没有办法简化成“链”或者“树”的形式。如果依然使用监听被依赖项的变化来更新表单,会产生重复更新的问题。如上图中的依赖关系,当“地域”更新之后,会同时触发“套餐包内容”和“可用区的变化”,最终导致“购买时长”触发了两次更新。

使用拓扑排序生成依赖更新序列

基于表单依赖关系是一个有向图,如果能够保证这个图中是一个有向无环图,我们就可以使用拓扑排序来生成一个表单项的更新序列。

简单介绍拓扑排序的思路,就是每次从当前图中找出入度为0(没有箭头指向)的点,压入队列,并从图中删除该节点,最终生成一个排序队列。以上文中的例子来看,我们就可以得到一个更新队列:['套餐包类型', '地域', '套餐包内容', '可用区', '购买时长']。(1、队列顺序并不唯一;2、因为笔者表示依赖关系的有向弧指向和典型的拓扑排序的要求相反,这里实际实现时是取了出度为0的点)

在有了这样一个更新队列之后,可以集中管理表单的依赖关系。在监听到表单项的更新之后,拷贝一个表单的草稿,按照更新队列的顺序更新草稿,再整体更新表单。比如(React DEMO):

import produce from 'immer';

// ...

const onChange = (key, newValue) => {
  const newValues = produce(values, draft => {
    // 更新草稿draft
  });
  setFormValues(newValues)
}

// ...

<Form value={values} onChange={onChange}>
{ // ... }
</Form>

确保有向无环图的成立

拓扑排序的成立条件之一是当前的图中不存在环。在依赖图中,如果出现了环形结构,就意味着依赖关系中存在循环依赖。大部分情况下,循环依赖的关系在需求阶段就可以发现并避免;但仍然有某些情况下,循环依赖会不那么容易被发现。比如下面的一段描述:

在选择“省份”后,“高校”一栏只能选择该省份的学校;在选择“高校”之后,“省份”一栏自动选取当前学校的所在省份。

在这里“省份”和“高校”之间就产生了一个循环依赖。产生这种问题的原因,就是表单在初始情况下的依赖关系不明确,各个表单项处在一个全部可选的状态。规避这种问题,就需要将表单项的依赖关系收敛到一个不存在环的状态,笔者想到的解决方案有两个:

  1. 在开始时明确指定依赖关系,存在依赖的表单项处于“全部不可选”的状态。只有当该表单项的所有依赖项都确定了取值之后,才能更改。
  2. 在开始时明确指定依赖关系,并给出一个表单的默认值。此时表单的状态成为状态机中的某个具体状态节点,就不会产生混沌不清的依赖关系。

后记

前端对于表单的解决方案已经有很多很好的实践。比如react-final-form等优秀的开源框架,能够帮助我们解决大部分表单需求。只是在遇到表单需求时,有时候并不能简单地一把梭,而是需要我们对问题进行一些简单的分析。首先确保当前需求明确、设计合理,之后再着手coding,才能达到事半功倍的效果。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • useMemo & useCallback 指北

    很多时候我们在学习新东西之后总是会很兴奋地去做各种尝试。在React hooks正式面世之后,团队也在很多业务中开始尝试使用这种新语法。除却提及最为广泛的use...

    暂七师黑管手
  • 搭建自己的脚手架

    最近接手了一个内部配置运营平台,大概了解了代码结构之后,第一波优化就是搭建了一套脚手架。

    暂七师黑管手
  • 一文彻底搞清Gradle依赖

    作者:曾是放牛娃 https://www.jianshu.com/p/59fd653a54d2

    用户1269200
  • 依赖倒转原则(笔记整理)

    已经是2个月没有写过博客了吧,打开自己的博客,突然有种亲切感。给老板干活的日子很苦,能够有点属于自己的时间真是一种享受。

    卡尔曼和玻尔兹曼谁曼
  • 2015年中国移动互联网城市竞争力调查

    报告显示,截至2015年上半年,中国手机网民规模已经达到6.565亿,移动互联网行业的发展呈现出明显的地域性特色,并且和经济发展高度同步。主要分布在以北京为中...

    灯塔大数据
  • c# 类间关系

    一、依赖关系     简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。比如某人要...

    wfaceboss
  • Flutter开发:项目加载本地html文件的步骤

    Flutter开发会遇到各种各样的技术,而且flutter开发带来了新的“技术革命”,解放了iOS单一开发和Android单一开发所带来的巨大成本问题,一套fl...

    三掌柜
  • 本博客百家号通过新手期的经验总结分享

    晚饭后习惯性的进入百家号后台才发现竟然收到了百家号新手期通过审核的通知,哈哈。自从二月份百家号指数超过 500 后一直都在申请终于通过了,真是有志者事竟成呀!今...

    明月云服务
  • 浅析网站更换ip或使用CDN会不会影响SEO排名

    最近张戈博客在阿里云和腾讯云服务器之间来回折腾了数次,别的收获没有,就悟出了一个问题:网站更换 IP 或使用 CDN 会不会影响 SEO 收录或排名? 收录就不...

    张戈
  • Nginx在CDN加速之后,获取用户真实IP做并发访问限制的方法

    老七Linux

扫码关注云+社区

领取腾讯云代金券