表单演进之路(一): 简单表单管理

298 阅读6分钟

背景

网页交互总是会需要用户填写数据提交,这种交互方式在远古时期统称为表单操作。随着互联网的发展,表单的核心流程依然是

"填写字段 -> 点击提交"

但如今表单所需要做的事情早已没那么简单,本篇系列会讲述我自己对表单的思考

特别说明 本文涉及到vue知识,在使用到的地方会贴出对应的文档地址。本文将以读者以熟悉知识点进行讲解。

表单之路

最简单实现一个表单

W3C规定使用form来定义一个表单,通过几个语义化的HTML标签书写,我们可以轻松的实现一个表单

实现一个登录表单

这种方式实现的表单简单直观,可以清晰知道目前的代码对应的实际效果

情况1: 根据条件变化实现一个表单

情况1解法1: 整个表单进行独立控制

如果表单有很大不同可以将表单当作一个组件通过v-if进行直接切换表单,在登录注册实现下,没有太大问题。但如果涉及到其他步骤,就要逐渐增加form

根据条件切换表单

这种方式的优点在于各个表单之间独立,移除表单时只需整个表单移除即可。

情况1解法2: 表单项进行独立控制

上述整个表单的方案并没有实现代码之间的复用,如果涉及多个相同的字段,代码重复率会直接上升。所以我们可以使用另一种写法

表单项进行独立控制

这种方式的实现,不会重复定义字段,减少的重复的代码,bug修复或者增加新功能时,只需修改此文件即可。

情况2: 表单与在不同情况下展示不同ui

pc端展示效果 pc端展示效果

移动端展示效果

移动端展示效果

情况2解法1: 完全独立两套实现

这样实现好处是两端完全独立隔离,不会导致bug两端同时存在。缺点是需要2倍的开发工作量,而且其中涉及负责的逻辑处理代码,皆会成为重复实现的代码。

情况2解法2: 通过两套css实现,特定地方通过v-if进行控制

通过两套css实现,特定地方通过v-if进行控制

  1. 理论上,这里的通过不同情况加载不同的css,对样式进行统一控制。但实际,样式也和标签定义的位置顺序也有依赖关系,这种方式过于理想化,而且对css的掌握水平要求很高
  2. 内部的v-if同时含有环境的判断,这样会使条件变得复杂

情况2解法3: 逻辑层与ui展示层脱离实现

逻辑层与ui展示层脱离实现

v-for文档

const fields = [{
  key: 'account',
  label: '账户名',
  type: 'input',
  required: true
}, {
  key: 'password'
  label: '密码‘,
  type: 'password',
  required: true
}, {
  key: 'second_password'
  label: '二次确认密码‘,
  type: 'password',
  required: true
}];

这种方式实现设计思路如下

具体实现 = 逻辑配置 + 渲染模版

好处如下

  1. 逻辑代码和ui展示进行了分离,逻辑代码进行公共化,只需更换渲染模版就能兼容不同环境之间的ui展示
  2. 保证了最大圈复杂度,复杂度为字段所用到的组件数
  3. 段字段相互进行隔离,只需控制每个字段对象而不是通过v-if直接控制HTML

情况3: 表单项具有特殊交互,联动

情况3解法1: 通过动态组件实现

特殊处理的表单项,在通用模版里,可以定义为vue动态组件,在组件内实现特殊的交互及联动效果。 vue动态组件

优点

  1. 对于表单来说,这个表单项与其他表单项使用效果相同,通过统一定义实现的组件都可以直接在表单内使用,无需每次修改表单。
  2. 因为每一个特殊交互的表单项都是一个组件,这个组件可以复用在任意地方。
  3. 因为交互和联动逻辑都写在组件之内,修改删除相关功能只需在组件内修改即可,不用担心影响到表单其他项的正常执行。

如何复用表单

根据逻辑上的判断,可以将不同表单区别归结为表单字段的的属性动态变化。按以下逻辑我们可以将“可变”的部分抽离。

表单 = 表单模板 + 字段声明

以上述的代码继续衍生,我们需要管理字段声明

简单字段声明,每个字段声明为一个对象,每个表单字段声明为一个数组

// 配置文件定义
const accountField = {
  key: 'account',
  label: '账户名',
  type: 'input',
  required: true
};

const passwordField = {
  key: 'password'
  label: '密码‘,
  type: 'password',
  required: true
};

const secondPasswordField = {
  key: 'second_password'
  label: '二次确认密码‘,
  type: 'password',
  required: true
};

const email = {
  key: 'email'
  label: '邮箱‘,
  type: 'input'
}

// 用于注册的配置文件
const signUpFields = [
  accountField,
  passwordField,
  secondPasswordField
];

// 用于登录的配置文件
const loginFields = [
  accountField, 
  passwordField
];

// 用于重设密码的配置文件
// 用于重设邮箱的配置文件
// 代码中引用配置文件
let fields = [];
swtich(type) {
  case 'signUp': fields = signUpField;break;
  case 'login': fields = loginFields;break;
}

实际使用的配置文件 = 多个配置文件 && 控制配置文件逻辑

具体实现 = 渲染模板 + 实际使用的配置文件

优点

  1. 每个配置都能知道具体的职责和对应的表单项
  2. 移除时只需整个移除配置文件即可
  3. 单个配置里表单项目的增加删除不影响其他配置

缺点

  1. 对于多个配置之间所用到的公共字段进行修改,容易遗漏
  2. 当同一份配置文件,只有部分属性进行变更时,仍需要整个重新定义
  3. 对于引用配置文件的地方来说,需要手动多次引用并通过if控制实际读取的配置文件(此段逻辑不方便直接复用)
  4. 当控制配置文件的逻辑不只一个维度时,会出现复杂嵌套的if
// 多维度时使用此方式定义表单项,不同区域手机号验证不一样
let fields = [];
if(region === 'Chinese') {
  if(type === 'signUp') {
    fields = chineseSignUpFields;
  } else if(type === 'login') {
    fields = chineseLoginFields;
  }
} else if(region === 'America') {
  if(type === 'signUp') {
    fields = americaSignUpFields;
  } else if(type === 'login') {
    fields = americaLoginFields;
  }
}

总结

以上表单实现方式在某些地方总会碰壁,在ToB业务下,更是需要迎接对多维度的挑战。通过简单对象方式时,每增加一个维度,就会增加一层if嵌套,如何设计一个优秀的表单声明管理就显得非常重要。

未完待续 下一篇:表单演进之路(二): 多维表单管理