背景
网页交互总是会需要用户填写数据提交,这种交互方式在远古时期统称为表单操作。随着互联网的发展,表单的核心流程依然是
"填写字段 -> 点击提交"
但如今表单所需要做的事情早已没那么简单,本篇系列会讲述我自己对表单的思考
特别说明 本文涉及到vue知识,在使用到的地方会贴出对应的文档地址。本文将以读者以熟悉知识点进行讲解。
表单之路
最简单实现一个表单
W3C规定使用form来定义一个表单,通过几个语义化的HTML标签书写,我们可以轻松的实现一个表单
这种方式实现的表单简单直观,可以清晰知道目前的代码对应的实际效果
情况1: 根据条件变化实现一个表单
情况1解法1: 整个表单进行独立控制
如果表单有很大不同可以将表单当作一个组件通过v-if进行直接切换表单,在登录注册实现下,没有太大问题。但如果涉及到其他步骤,就要逐渐增加form
这种方式的优点在于各个表单之间独立,移除表单时只需整个表单移除即可。
情况1解法2: 表单项进行独立控制
上述整个表单的方案并没有实现代码之间的复用,如果涉及多个相同的字段,代码重复率会直接上升。所以我们可以使用另一种写法
这种方式的实现,不会重复定义字段,减少的重复的代码,bug修复或者增加新功能时,只需修改此文件即可。
情况2: 表单与在不同情况下展示不同ui
pc端展示效果
移动端展示效果
情况2解法1: 完全独立两套实现
这样实现好处是两端完全独立隔离,不会导致bug两端同时存在。缺点是需要2倍的开发工作量,而且其中涉及负责的逻辑处理代码,皆会成为重复实现的代码。
情况2解法2: 通过两套css实现,特定地方通过v-if进行控制
- 理论上,这里的通过不同情况加载不同的css,对样式进行统一控制。但实际,样式也和标签定义的位置顺序也有依赖关系,这种方式过于理想化,而且对css的掌握水平要求很高
- 内部的v-if同时含有环境的判断,这样会使条件变得复杂
情况2解法3: 逻辑层与ui展示层脱离实现
const fields = [{
key: 'account',
label: '账户名',
type: 'input',
required: true
}, {
key: 'password'
label: '密码‘,
type: 'password',
required: true
}, {
key: 'second_password'
label: '二次确认密码‘,
type: 'password',
required: true
}];
这种方式实现设计思路如下
具体实现 = 逻辑配置 + 渲染模版
好处如下
- 逻辑代码和ui展示进行了分离,逻辑代码进行公共化,只需更换渲染模版就能兼容不同环境之间的ui展示
- 保证了最大圈复杂度,复杂度为字段所用到的组件数
- 段字段相互进行隔离,只需控制每个字段对象而不是通过v-if直接控制HTML
情况3: 表单项具有特殊交互,联动
情况3解法1: 通过动态组件实现
特殊处理的表单项,在通用模版里,可以定义为vue动态组件,在组件内实现特殊的交互及联动效果。
优点
- 对于表单来说,这个表单项与其他表单项使用效果相同,通过统一定义实现的组件都可以直接在表单内使用,无需每次修改表单。
- 因为每一个特殊交互的表单项都是一个组件,这个组件可以复用在任意地方。
- 因为交互和联动逻辑都写在组件之内,修改删除相关功能只需在组件内修改即可,不用担心影响到表单其他项的正常执行。
如何复用表单
根据逻辑上的判断,可以将不同表单区别归结为表单字段的的属性动态变化。按以下逻辑我们可以将“可变”的部分抽离。
表单 = 表单模板 + 字段声明
以上述的代码继续衍生,我们需要管理字段声明
简单字段声明,每个字段声明为一个对象,每个表单字段声明为一个数组
// 配置文件定义
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;
}
实际使用的配置文件 = 多个配置文件 && 控制配置文件逻辑
具体实现 = 渲染模板 + 实际使用的配置文件
优点
- 每个配置都能知道具体的职责和对应的表单项
- 移除时只需整个移除配置文件即可
- 单个配置里表单项目的增加删除不影响其他配置
缺点
- 对于多个配置之间所用到的公共字段进行修改,容易遗漏
- 当同一份配置文件,只有部分属性进行变更时,仍需要整个重新定义
- 对于引用配置文件的地方来说,需要手动多次引用并通过if控制实际读取的配置文件(此段逻辑不方便直接复用)
- 当控制配置文件的逻辑不只一个维度时,会出现复杂嵌套的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嵌套,如何设计一个优秀的表单声明管理就显得非常重要。
未完待续 下一篇:表单演进之路(二): 多维表单管理