这篇文章上次修改于 631 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
在某一天,小明同学拿到了一个前任(兼职)写出来的一个后台项目。
仔细一看,整个项目采用了 DvaJS(Redux 全局管理)的技术栈,感觉这玩意有点意思。但前任编写的代码全部是 JavaScript,没有 TypeScript,他起初以为这不算什么...
迷人的组件传参
直到某一天,小明同学需要开始正式接手这个项目的迭代开发,需要设计一个表单,而表单里面需要有一处上传图片功能的组件。他找到了前任写的「通用上传组件」。
这个组件有这么多 props 属性,它们都是干啥的呢?
- onRef 看起来是把组件里面的状态引到外面
- propsParams 看起来是告诉组件应该以什么效果展示,什么形式处理文件
- editDetailData... 这是干嘛的?
- logo 看起来是预设一张图片展示出来
- setFieldsValue 看起来是把上传的文件写到表单状态里面
- callBackFileList... 这又是干嘛的?
等他找到组件源代码之后搜索发现,这参数怎么没用到呢?更迷咯~
他试着全局搜索,发现用到这个组件的地方还都传了这个参数,却并不知道它到底有什么用
他只能忍声吞气,背负着这一屎坑组件,最后选择直接删除了所有的相关参数。而 editDetailData
这个参数,研究完代码之后看起来是将表单项目配合 logo
参数拿去填充 Antd 上传列表的标题去了。
这个二次封装的组件还有一个 State,这个 State 里面的 upload
最终作为 Antd Upload 组件的参数传了过去,更谜了。简单研究了下,如果要把现有表单的值回传,就只能通过 editDetailData
和 logo
传入给 Upload 组件,然后通过 触发 Upload 组件的 onChange
回调再调用父组件的 setFieldsValue
(最终父组件执行的是 Antd 的表单值设置方法)修改表单项,最终完成展示,感觉写的很离谱,但又不知道怎么吐槽...
这样的上传组件就把逻辑强制绑定在表单这个场景上使用了,非常不灵活,如果要切换到 Redux 托管表单数据的场景,状态设置混乱,就会产生很大的问题。
后续:小明同学最后选择基于 Antd 的 Upload 重构了一个同类组件,但改成了静态属性,仅接受首次传入的文件列表(value + onChange 配合表单组件的设计),后续组件外的状态修改(例如重新设置表单项的 value)均不会影响到组件的展示
后端需求变更,如何又产生一个新 Bug
某处业务表单用到了 Antd 3 的 CheckBox 组件实现多选功能,由于需求变更,这里被要求改成单选项。当小明同学将 CheckBox 换成 Radio.Group 组件之后点击表单自测发现,这貌似并没有问题。
// 表单某项的外部状态
const [checkedVal, setCheckedVal] = useState("")
...
// 表单提交前合并得到的数据
const _payload = {
[hasCommitStatus !== 'ADD' && 'id']: queryDeviceTypesDetail.id,
components,
index,
name,
// 注意这里,最终提交表单的时候再把这个表单外部的 State 拼接进去的
brand: checkedVal,
img: img[0] ?. url,
icon: icon[0] ?. url,
}
...
{/* 表单项 */}
<Form.Item label={languagePack.brand}>
{getFieldDecorator('brand', {
initialValue: checkedVal
})(
<Radio.Group
options={plainOptions}
// 就是下面这个 onChange,原作者就这么写的
// 其实可以直接用 Redux 和表单状态配合存值的,也不知道为什么他这么写
onChange={handleChangeCheckboxVal}
/>
)}
</Form.Item>
直到后续有人测试,发现这里选中的项目无法存储到后端,他仔细排查问题,其实是由于这里的回调从 number[]
变成了 Event
对象,这才揪出了原因,是他当初修改组件的时候并没有留意到回调参数类型的改变。
没有 TypeScript 的情况下就没有报错提示,而 Antd 的 Radio.Group
组件本身不需要更改后的 value
传回去都能独立运作,所以即便类型错误了,也能看到“正确”的效果。
// Before
const handleChangeCheckboxVal = checkedList => {
setCheckedVal(checkedList)
}
// After
const handleChangeCheckboxVal = ev => {
setCheckedVal(ev.target.value)
}
这种绕开表单状态的写法本身就是愚蠢的,耦合性太高了,我认为这就是给后人埋下炸弹的行为== 没有 TypeScript 的纠错,这样的 Bug 就很容易藏在里面,不易察觉。
正确的思路应该是从 Redux 拿到数据的时候就应该提取出来,拼凑出表单(新增和编辑都得一样)需要的键和值,而不是拿到值之后丢给页面额外处理。这样提交的时候就可以直接读取 Antd 的表单项,而不应该是从 checkedVal
这个表单外状态中获取再进行拼接。
总结
通过这两个实际案例,可以说 TypeScript 在多人协作的项目里面还是能起到不小的作用,自带文档属性。它能把类型错误或无效的组件参数抛出报错,处理组件输出和回调的时候,也能提前检查比对返回类型,减少很多可能因传参错误,导致结果和预期不一致的问题。
异步提交表单的情况,也可以提前将后端接受的类型录入进行类型定义,可以保证提交表单内容各个字段的类型是正确的。
当然这所有的基础,都需要一个合格规范的后端代码,以及配套的类型定义,如果你的 TypeScript 代码里面出现了大量 any
和 @ts-ignore
,那就还不够合格,需要继续努力了!💪
已有 3 条评论
我司已有的几个项目就是,完全使用 JS 的 Vue 2 项目,前人留下的代码很糊,维护起来很头痛……后来一些页面只能推掉重做
新年快乐
一入前端深似海,指学习上的海和以前留下的坑的海(