今天在做一个发票列表的页面,整个项目是基于TypeScript
, React
, AntDesign
来开发。
有个功能是这样的:对于其中的每一个发票,在此发票拥有指定状态,同时访问此页面的用户拥有指定权限的时候。改用户可以变更发票的挂账月份。
这个功能在开发的时候,我就注意到了,在我之前做发票的详情页的时候已经做过了这个功能。
发票详情页的挂账月份修改功能的实现
首先配置了一个发票状态和修改权限的配置表
export const InvoiceModifyAuthority = {
toPayDate: {
status: [STATUS_1],
role: [ROLE_1],
},
gzDate: {
status: [
STATUS_3,
STATUS_6,
STATUS_9,
],
role: [ROLE_3],
},
};
并引入了isAbleModify
方法,传入配置和当前发票状态,它会自动获取当前用户的角色,然后进行判断,最后返回bool值,告知能否修改。
然后引入了一个功能组件ModalDatePicker
,它的功能是弹窗并在其中选择日期。
这个组件需要title
, defaultValue
, onCancel
, onOk
, visible
这些props。
为了能正确给ModalDatePicker配置上这些属性,我在页面组件的State
中对应加上了这几个key。
同时新增了showModalDatePicker
, hideModalDatePicker
, handleDateModify
这三个方法来服务于ModalDatePicker的显示、隐藏和值的回调。
而在handleDateModify方法中,为了实现发票属性的变更,又引入了updateInvoice
方法,并在成功修改之后,调用antd
(也就是AntDesign的包的实际名称)的message
功能来通知用户修改成功。
具体实施过程就会大概如下:
- 在render里面确认能够修改的时候,显示一个修改按钮。
- 按钮上绑定了click事件。点击之后设置调用showModalDatePicker,使得ModalDatePicker可见。
- 在弹窗ModalDatePicker上,用户选择取消,则隐藏弹窗不做任何事情,而如果进行了时间筛选并确定,就会调用handleDateModify方法,把用户选择好的时间传递过去。
- handleDateModify会
- 调用hideModalDatePicker隐藏弹窗
- 调用updateInvoice方法使用invoiceId和传递过来的日期来更新发票信息
- updateInvoice处理完毕之后调用message告知用户
现有实现的问题
现在,发票列表页也需要同样的修改账期的功能。
直接复制粘贴的方法来迁移这个功能相关代码,是一种实现方案,但很明显不是一个明智的选择。各种依赖的管理,冗余的代码,对功能变化的承受能力还有再遇到类似功能时候的快速实施方面都不理想。
为什么已经实现过的功能再别处需要使用的时候会这么麻烦。因为最初实现里面包含了太多的细节:权限判断,交互实现,更新的功能实现,操作结果的反馈。
这么多的细节,那功能的迁移上自然也就无法简化。
重构
封装
要方便功能的复用,我们要做的就是封装,让一切我们不需要关注的细节被隐藏起来。
让我们尝试着回到最开始,来重新解刨一下整个需求。 我们最直接的需求是什么:修改账期。
我们期待的方式应该是,有一个ModifyGzDate
的组件,我们提供invoiceStatus
, invoiceId
, currentInvoiceGzDate
和一个可选的onUpdateFinish
方法。而这个组件会自行完成权限和状态校验,自行处理用户修改挂账月份的操作并在处理完成之后,在设定了onUpdateFinish时,调用onUpdateFinish并传递最新值。
这样这个功能的迁移成本就非常的低廉。
在有现有功能的情况下,封装这个需求还是比较容易实现的
拆分和组合
上面的ModifyGzDate是能很好的满足修改挂账月份这个需求的,可是如果我现在还需要一个修改发票的付款月份的功能呢。
所以上面的ModifyGzDate需要进行一些拆分,可以拆分成三个部分:权限判断,交互,接口。
实现一个DateUpdater
的组件,接受initDate
和handleDateChange
两个属性,把弹窗显隐,时间筛选等细节封装进去。
再基于DateUpdater来具象化一个InvoiceDateUpdater
组件,接受initDate
, invoiceId
, modifyKey
和可选的onUpdateFinish
。这个组件主要是具像化了DateUpdater的handleDateChange, 根据invoiceId, modifyKey和最新的日期值来调用updateInvoice更新发票信息。并在完成之后看情况回调onUpdateFinish。
最后就是重构ModifyGzDate
,根据传入的invoiceStatus来判断是否有权限修改挂账月份,如果有,则调用InvoiceDateUpdater组件
再这样的重构完成之后,如果要新增修改付款月份的功能。就类似ModifyGzDate一样,完成权限判断之后,根据情况调用InvoiceDateUpdater。
总结
上述的重构完成之后,对于后续发票这块的功能的复用和扩展来说就是很方便了。里面用到的主要的设计思想就是单一职责的原理。尽可能的去拆分你的逻辑,让每个模块的功能尽可能的单一,这样就可以在需要的时候,轻易的组合和封装这些单一的模块,从而实现一些特定场景下的功能组件。