![c6201440b617a949eac87faa1ed096df.png](https://i-blog.csdnimg.cn/blog_migrate/fd3a7fdc67e527784809491ccd820b78.jpeg)
前言
此时更新主要带来了 v-model 功能,并加入了 cypress 做 e2e 测试来护航。下面一起来回顾一下开发小哥此次版本迭代的精彩历程吧~
目标:v-model
功能:v-model
这次 feature 版本更新,主要是为了 v-model 功能开展的。其可以解决以下问题:
- 不再需要对 ref 操作 updateForm、getFormValue 来与数据交互
- 可以直接观察当前的表单数据变更,来进行特定操作
后续对 valid 状态,也通过 prop + sync 的形式管理,基本就可以脱离对 ref 的依赖。
提升代码质量
由于 el-form-renderer 已经是历时两年多的开源项目,许多代码经多人转手,已经存在以下问题:
- 不合适的写法。比如大量使用 render 函数;
- 大量使用 mixin 和字符串拼接来调用功能,开发者难以定位调用链条。比如在生成 option 数组时;
- 废弃的功能。比如不太好用的 enableWhen 等等
- 没有注释的 hack。比如尝试兼容 select, radio & checkbox 用法的代码(在1.14.1版本修复)
所以提高维护性也是本次迭代的重中之重。
下面就开始着手开发。
♻️ 测试
https://github.com/FEMessage/el-form-renderer/pull/159github.com第一步,就是加入自动化测试。
先前,项目中已经引入了 jest 做单元测试。单元测试可以保证一些核心逻辑的稳定性,比如一些数据处理、转换函数的输入输出意图。但是其没有办法测试真实示例使用起来时的稳定性,比如用户真正在表单中输入值、点击重置按钮的行为。此前,行为的稳定性只能依赖开发者和 review 人员手动对示例进行测试。
现在,项目使用 cypress 来对每个示例的行为进行自动化测试。这样开发者可以放心重构和添加功能,同时轻松维护过往表现的一致性。
如何写行为测试
重点应当关注用户行为层面,而不是代码输出层面。比如,应当测试:
- 用户输入文本
- 确认文本框中有该内容
- 用户点击提交按钮
- 确认窗口内显示成功信息
而不是:
- 用户点击按钮
- 等待某个接口有返回内容(这测试的是按钮有没绑定到函数,和接口是否正常
重构
https://github.com/FEMessage/el-form-renderer/pull/161github.com重构的主要成果是:
- 用 template 重构了所以 render 函数。代码更符合 Vue 规范写法
- 移除了 mixin。将相关功能转化成纯函数,并补充单元测试用例
- 层级调整。vue 组件移到 components 目录下;js 文件移到 util 目录下
- 优化函数命名。比如使用具体的
removeDollarInKey
替代transformItem
- 梳理了关键复杂点的逻辑,并提取成纯函数,补充单元测试。包括:
- transformInputFormat(用 inputFormat 处理初始和 updateForm 的值)
- transformOutputFormat(用 outputFormat 处理 getFormValue 的值)
- collect(从 content 中搜集 options 和初始 value)
- 补全了所有关键示例的端测试
inputFormat & outputFormat
这次重构的最大难点,就是处理 inputFormat、 outputFormat、group 与表单值 value 的逻辑。直接给出定义的话:
- 每个表单项的 inputFormat 接受当前 这一层(group) 的值,返回 当前项的值
- 每个表单项的 outputFormat 接受 当前项的值 ,返回的值先判断是不是对象:
- 如果不是,返回值视作 当前项的值
- 如果是,返回值将整体覆盖到 这一层(group) 的值
具体看如下示例。使用到的同学请务必注意其用法。
inputFormat
作者之前以为
// content 配置
{
id: 'a',
inputFormat: v => v + 1 // 接受传入值,返回新值
}
//
this.$refs.form.updateForm({a: 1})
this.$refs.form.getFormValue() // {a: 2}
实际上
// content 配置
{
id: 'a',
inputFormat: formValue => formValue.notA // 接受整个 formValue 来处理!
}
//
this.$refs.form.updateForm({notA: 1})
this.$refs.form.getFormValue() // {a: 1}
outputFormat
作者之前以为
// content 配置
[
{
id: 'a',
default: 1,
outputFormat: a => a + 1 // 接受内部值,返回处理过的值
},
{
id: 'b',
default: {c: 2},
outputFormat: b => (b.c += 1, b)
},
]
//
this.$refs.form.getFormValue() // {a: 2, b: {c: 3}}
实际上
// content 配置
[
{
id: 'a',
default: 1,
outputFormat: a => a + 1 // 这个的理解是对的
},
{
id: 'b',
default: {c: 2},
outputFormat: b => (b.c += 1, b) // 当返回值是对象时,会整体覆盖到上一层!
},
]
//
this.$refs.form.getFormValue() // {a: 2, c: 3}
✨ 开发功能:v-model
https://github.com/FEMessage/el-form-renderer/pull/162github.com开发过程一度顺风顺水:
- 新增属性 form,作为对外 v-model 的属性
- 在 watch 里对接数据流水线:
- 监听 form、content 的变更 -> 搜集初始值 --inputFormat--> value
- 监听 value 的变更 --outputFormat--> form
- 写新的示例 v-model.md 和测试 v-model.spec.js(参考 basic 即可)
这时 cypress 报告了一个 诡异的现象。
有 bug ?
先简单说一下测试用例流程:
- 输入各种 input、select、radio
- 检查输入后 v-model 的状态
- 点击 reset 按钮,v-model 回到初始状态
诡异情况发生了:
- 在模拟点击 reset 按钮时,发现 cypress 测试里点击重置按钮, reset 未生效?
- 作者 手动在 cypress 的调试浏览器中点击按钮,reset 生效了!
- 在 reset 函数里打点,发现 reset 函数在以上情况中 都有被正常调用
- 尝试在 cypress 代码中 先等待 1秒再点击按钮,reset 未生效
- 尝试在 cypress 代码中 连续写两次 click 按钮操作,reset 又生效了!
是 cypress 的问题?不管了直接在测试用例里写注释吧,就说明手动测试是 ok 的,连点两次是 hack 写法。 —— 我脑海里有个声音飘过。
好在作者冷静了点,利用这个契机好好地 review 了下原本的 resetFields 逻辑,发现了如下问题:
- reset 后 el-select 报错的 bug
- reset 后,value 监听器监听不到改变,因为 el-form 的实现机制中改了 value 后没有 emit input 事件
- 如果在监听器上加 deep: true,则会发现新值和旧值居然是一样的。原因暂时不明
在发现以上问题基础上,我对代码进行如下修改:
- 在 form、value 监听器中用 lodash 的 isequal 判断前后的值,有变更时才 emit input 事件
- 参考 el-form 机制在内部实现了 resetFields:用的是 mounted 时传入的值,然后清除校验错误信息
现在用例一切正常了,诡异情况也没有再出现。虽然到头来我仍没有弄清楚 为什么 cypress 的模拟操作代码调用的 resetFields 没有正常运作 ,但其恰好提醒了我此时有异常表现,我才发现了 resetFields 的其他问题。重视测试 的行为让组件得到了回报。
关于初始状态
值得一提的是,目前 resetFields 所认定的 初始状态,是在组件 mounted 阶段时 v-model 的值。这点与 element 一致,因为一些 element 表单组件,会在 created 阶段,在传入的 value 值不合法时会重新 emit 一个正确的初始值给到 v-model。比如:
- 开启了 multiple 的 el-select,会修正初始值为
[]
- el-checkbox,会修正初始值为
[]
(由 el-form-renderer 实现,原生是没有的哦)
更新文档
https://github.com/FEMessage/el-form-renderer/pull/163github.com此次更新顺带修复了一些老示例的错误用法。可能不少 el-form-renderer 的用户还不了解,el-form-renderer 的 disabled 属性,统一在配置最上层级设置:
// content 定义
[
{
disabled: true, // 在这里设置
el: {
disabled: true // 无效
}
}
]
意外之喜
Femessage El Form Renderer Style Guidefemessage.github.io在写 v-model 数据流水线时,因为把 content 也融入在其中,所以现在可以在运行时修改 content,并观测表单变化了!
结语
el-form-renderer 虽然一路遭遇到吐槽很多,但更多的是忠实粉丝们孜孜不倦地为其提交代码。直到今天,可以看到其相对于原生 element,开发体验已经有了长足的进步。现在有了端对端测试,开发和维护功能更是如虎添翼。这里感谢已为开源作出贡献的开发者们,也欢迎更多小伙伴加入我们 deepexi 开源生态中哦~