最近在开发支付页时遇到了写问题,记录如下:
首先介绍一下业务需求。支付页面有多种vip套餐类型可供用户选择。用户进入支付页会调一个接口来获取用户是否有优惠券可用,每种优惠券的使用门槛不一样。
这里为了讨论方便,用vip_type指代用户选择的vip类型,coupon_id指代用户选择的优惠券id。
需求一:当用户有多种可用的优惠券时,在用户切换vip时会自动将优惠券更换为面额最大的优惠券。
需求二:支付页面的二维码,生成需要订单号,获取订单号需要vip_type和coupon_id;
首先将vip_type和coupon_id分别用两个state来存储
为了实现需求一,我先使用一个依赖数组中放置了vip_type的useEffect来完成coupon_id的随vip_type切换重新动态选择的逻辑。
useEffect(() => {(
async () => {
// 获取优惠券列表
const couponist = await getCouponList();
// 计算获取面额最大的优惠券
const coupon_id = chooseCoupon(vip_type);
setCouponId(coupon_id)
})()
}, [vip_type])
为了实现需求二,我在生成订单号的useEffect的依赖数组中同时放置了vip_type和coupon_id这两个state,然后操作起来发现,每次切换vip类型时,生成订单号的useEffect都会执行两次,一次是vip_type这个state变化时触发的,当vip_type变化时又会触发coupon_id变化,这又会导致生成订单号的useEffect再执行一次,useEffect第二次才能拿到正确的订单号。
useEffect(() => {
(async () => {
// 获取订单号
const orderId = await getOrderId(vip_type, coupon_id);
setOrderId(orderId);
})()
}, [vip_type, coupon_id])
为了解决useEffect执行两次的问题,我有三种方案
方案一,不使用useEffect来生成订单号的函数,改为在需要的地方手动调。
将生成订单号的接口调用从原先的useEffect中拿出来,单独写成一个函数,并将vip_type和coupon_id当做该函数的参数。然后在切换优惠券的click中调用该函数,在每次切换vip类型后重新计算选择的优惠券的useEffect中也调用一下该函数。
方案二,将切换vip_type的click函数改为async,每次切换vip后重新设置coupon_id的逻辑不再放在useEffect中,而是放在vip_type切换的click函数里,这样保证了vip_type和coupon_id同时更新(在同一个批量处理中更新)。同时还要考虑初始化时候的处理。
所以需要将vip_type切换的click函数抽离出来,应用于vip_type切换和初始化时的useEffect。由于vip优惠券有时效性,所以一般在计算使用哪个vip优惠券时,需要调取后端接口。这会导致用户在切换vip_type时,ui的展示会有一定延迟感,交互上欠佳。
方案三,问题的核心点在于保证生成订单号的两个变量,是同时更新的。同时由于这两个变量之间又有依赖关系,coupon_id需要依赖更新后的vip_type来做更新。
可以考虑新增一个专门供生成订单号的useEffect依赖的state, 这里我们就叫vip_type_for_effect,它的值和vip_type相同,但更新时机和coupon_id一致。这样就可以保证获取订单号的useEffect只执行一次了,同时保留vip_type,用来保证ui的展示及时性。
比较三个方案,方案二有交互上的欠缺,方案三可以看做方案二的升级版。方案一和方案三都差不多,个人在这次需求的实现中选择的是方案一。