最近做了个复杂的表格,中间遇到了不少问题,本文简单记录一下。
需求
- 1、表格 A,嵌套子表格 B
- 2、表格 A、B 均支持拖拽排序
- 3、表格 A 支持增删子表格 B
- 4、表格 B 支持增删表项
- 5、表格 B 中的表项中包含了下拉框、开关、输入框等表单元素,支持直接修改数据
- 6、表单之间存在依赖关系(比如打开关闭状态下输入框被禁用,打开状态下要求输入框必填),需要支持完整的校验、提交接口的流程
从 1 到 6 是我个人认为的从简单到困难的顺序,分析一下(以下为纯主观看法):
- 1-4:比较简单
- 表格使用 Antd 的 Table 组件
- 子表格功能通过 Table 的展开项
expandable
实现 - 拖拽排序功能使用 react-sortable-hoc
- 增删表项,表格数据在受控方式下直接修改即可
- 5-6:一涉及到表单就变得复杂了
- 表单本身需要关注受控与非受控的数据管理问题
- 表格是两层,存储的数据结构也是两层,在更新第二层的某个状态时的逻辑比较麻烦
- 对于表单元素而言:
- 如果使用受控方式,那修改状态会使得整个组件重新渲染,导致表单(比如开关)丢失切换动画(勉强能接受)
- 如果使用非受控方式,就应该使用 Form 组件进行数据收集
- 附加因素,并不是说下面这俩本身有多困难,而是它们的存在导致 5-6 变得更加复杂了
- 7:使用 React,主要是没有 Signal 机制的数据与视图关联逻辑
- 当 state 多层嵌套过于复杂时,想要修改其中很深层的某个数据就会非常麻烦
- state 变化导致整个组件 re-render,而表格数据过多,每个表单项都需要支持修改,re-render 次数要爆炸了,对它做优化也不是个容易的事情
- 8:Form 组件在包 1 中,经过打包后在包 2 中使用
- 包 1 在构建时没有剔除掉 Antd 的依赖,这样会不会导致包 2 使用 Form 时与包 1 中的 Form 产生冲突
- 7:使用 React,主要是没有 Signal 机制的数据与视图关联逻辑
遇到的问题
1、子表格能打开但无法收起
- 场景:表格 A 设置了拖拽排序,表格 A 带有展开的子表格 B
- 问题:表格 A 中点击后,可以展开子表格 B,但是再次点击无法收起
- 解决方案:使用受控方式自行维护
expandedRowKeys
2、子表格中的表单数据无法被收集
- 场景:表格 A 设置了拖拽排序,子表格 B 中使用了表单元素 Input 等
- 问题:如果某一个子表格未曾展开过,不会渲染组件,也不会被 Form 组件收集到
- 分析:猜测是展开项本身在收起的时候会被销毁,导致 Form 组件无法获取到其内部的数据
- 解决方案:未解决,完全没法使用 Form 收集这个数据了
3、子表格中的表单校验
- 场景:子表格 B 中使用了表单元素 Switch、Input 等
- 问题:使用 FormItem 收集表单数据,校验时子表格中所有表单都会展示错误样式(包含那些实际校验通过的表单)
- 分析:
- 解决方案:未解决,不使用 Form 的理由 + 1
4、外层拖拽样式混乱
- 场景:表格 A 设置了拖拽排序,且当前已展开了某些子表格 B
- 问题:此时拖拽 A 的某行,其子表格 B 不会跟随,展示错乱
- 设计需求:拖拽开始前的一瞬间收起所有展开的子表格
- 存在问题:拖拽排序有拖拽前的钩子函数可以用,但是由于 1 的问题,当前展开项已经换乘了受控方式。此时对状态做修改,会导致组件重新渲染,拖拽动作立刻被打断。无法实现。
- 凑合方案:拖拽时不管,在拖拽结束时收起所有展开项。
5、两层结构的表单元素数据更新
由于无法使用 Form.Item
收集数据(见问题 2、3),所以自行用 state 存储整个两层的表单数据。
处理逻辑:下拉框、开关 onChange
的时候,修改上一级组件内的 state,使当前组件重新渲染。
在此情况下:
- 能实现表单关联,比如开关关闭时,禁用输入框
- 丢失组件动画,比如开关切换的动画
- 输入框不能使用
onChange
受控,因为会导致整个列表重新渲染,输入框会立刻失去焦点,无法正常输入
不是很好的解决方案:
输入框采用直接修改对象值的方式(实在想不出在纯函数的方式下怎么搞),不触发组件渲染,这样既能够正常输入,也能通过 Form 获取到绑定到数据。