关于如何优雅的管理小程序跳转逻辑的实践

前言

注意:本文中的代码举例都是通过Taro 2.x去实现的。

微信小程序由于其便捷性受众用户也有很多,对于产品端而言能通过一个二维码简单快速的进入小程序指定功能页面,无疑能够极大的增加用户的体验评分;因此我们在生活中可以发现很多微信小程序在开发中都有扫指定二维码跳转某一页,或者通过关联微信公众号去跳转某个小程序页面的操作流程,并且根据业务的大小与需要,这种类似需求可能有很多。

如果要找一个很典型的现实场景的话,那目前最常见的由于防疫需要所出现的场所码,它就是这样子做的。
用图大概描述一下就是下面这样

原有流程:

image.png

期望实现的流程(当然是没有副作用的跳转,至于可能的副作用会在下文中讲)

image.png

分析

基于上述的场景,我们先来模拟一个详细的需求场景吧

准备好了吗?产品小姐姐要为我们提需求了;

第一个需求

现状是目前我们用户想要了解我们的产品详情只能通过进入小程序后,通过一步一步的操作,“历尽千辛万苦”才能找到自己想看的产品详情;如下图:

image.png

基于上述现状产品小姐姐说了,我们需要做一个用户通过微信扫一扫,扫描对应的产品二维码,就能看到当前产品详情的功能,明天上线!如下图:

image.png

那么作为一名光荣的开发,当你拿到这样的需求时该怎么做呢?

做法

其实开发过小程序的同学肯定知道,这个实现起来没有一点难度,无非就是和安卓端(假设二维码的生成是安卓端搞的)约定指定的参数,我们在小程序内部再根据扫码得到的参数去控制跳转到对应的实现页面即可!于是我就噼里啪啦写了下面的代码(什么?需要设计?不存在的!):

componentDidMount(){
 // 小程序登录操作
 login();
 // 在首页直接判断当前是否=满足跳转商品详情页的条件
 if(scanType === 'goodsDetail'){
   // 满足条件后直接跳转对应页面,并return出来(防止下面可能会有其他跳转的场景)
   Taro.navigateTo({
     url: '/pages/goodsStore/goodsList/detail?goodsInfo=xxx',
   });
   return
 }
 // 其他逻辑代码...
}

好了,开发、验收、送测、预发完成没有问题,完美,顺利上线!!🎉

第二个需求

在第一个需求实现后,产品尝到了这种实现方式的甜头,开始规划了n个功能

  1. 新增活动商品推送,点击关联公众号的活动商品卡片直接进入对应的提示支付页面;
  2. 扫码后跳转到vip开通页面;
  3. 跟售卖平板设备绑定后,扫码登录平板;(参考海底捞点菜平板)

做法

这次的需求里面唯一不同的就是增加了关联公众号跳转的实现方式,其他的两个和第一个需求类似,不细说;我们只看关联公众号跳转的实现方式,它貌似看起来是一种新的实现方式?

其实通过关联公众号消息直接跳转小程序和扫二维码进入小程序的实现其实并没有什么两样,不同的是二维码链接是需要符合你在小程序后台配置的格式的(如下)。而关联公众号的跳转可以直接写对应的小程序路径即可。

image.png
老规矩,噼里啪啦一顿敲

componentDidMount(){
 // 小程序登录操作
 login();
 // 判断当前是否满足跳转商品详情页的条件
 if(scanType === 'goodsDetail'){
   // 满足条件后直接跳转对应页面,并return出来(防止下面可能会有其他跳转的场景)
   Taro.navigateTo({
     url: '/pages/goodsStore/goodsList/detail?goodsInfo=xxx',
   });
   return
   // 判断当前是否满足跳转活动商品支付页的条件
 } else if(scanType === 'activeGoodsDetail'){
   Taro.navigateTo({
     url: '/pages/goodsStore/goodsList/detail/payPage?goodsInfo=xxx',
   });
   return
   // 判断当前是否满足跳转vip开通的条件
 } else if(scanType === 'openVip'){
   Taro.navigateTo({
     url: '/pages/user/detail/vipPage?params=xxx',
   });
   return
   // 判断当前是否满足登录平板的条件
 } else if(scanType === 'loginPad'){
   Taro.navigateTo({
     url: '/pages/device/login?deviceId=xxx',
   });
   return
 }
 // 其他逻辑代码...
}

好了,又一次完美的完成目标!奖励自己吃鸡腿!

这个时候回头看看我们的代码,貌似还可以哈,除了判断多了点,return多了点,逻辑复杂了点,代码难解读了点,没啥“大”问题。

就这样吧,上线上线!我们的收获是什么呢?大抵是凭借超快的开发速度,收获了产品小姐姐的点赞,emmm,香!

第三个需求

随着国家加强了互联网整治力度,我们要做相应的隐私合规修改,避免被查封的悲剧发生。需求这不就来了嘛!

  1. 不能自动为用户小程序登录,我们需要用户手动确认登录;
  2. 为了兼顾未注册用户的体验,决定通过扫码查看商品详情时,不获取用户信息。(并且明确说了,以后这个情况只会多不会少);

小小的分析一波:

对于第一点,这个好处理,我们只需要把确认登录的操作权限开放给用户即可;

实现思路基本就是在请求数据全局监听token失效的code(“403”),监听到用户登录失效之后跳转到登录页面即可!

对于第二点,我们要先了解一下目前的流程,大概是如下样子:

image.png
换句话来理解就是在通过某些途径进入指定页面时,我们不能让它走正常的 进入小程序 -> 获取用户信息 流程,而是需要在指定途径才去走获取用户信息的流程,这个想起来貌似也很简单呐~

思路已经有了,接下来就是噼里啪啦时间~

示例代码如下,

首先实现登录页面的代码:

function login(){
const allPage = Taro.getCurrentPages();
  const pages = allPage[allPage.length - 1];
  // 获取到当前页面的url以及对应的参数,此处时为了登录成功后可以返回上一个操做页面
  const { route, options } = pages;
  let queryInfo = '';
  for (const key in options) {
    if (Object.hasOwnProperty.call(options, key)) {
      const decodeQuery = decodeURIComponent(options[key]);
      queryInfo += `${key}=${encodeURIComponent(decodeQuery)}&`;
    }
  }
  // 构造返回页面的路由
  const uri = `/subpackages/otherPage/loginWXConfirm/index?backUrl=${
    allPage.length ? encodeURIComponent('/' + route + '?' + queryInfo) : '/pages/device/index'
  }`;
  
  // 此处判断是为了防止死循环
  if (route !== 'subpackages/otherPage/loginWXConfirm/index') {
    Taro.navigateTo({
      url: uri,
    });
  }
}

实现了登录后,其次我们需要实现第二点。

代码示例:

componentDidMount(){
 // 判断当前是否满足跳转商品详情页的条件
 if(scanType === 'goodsDetail'){
   // 满足条件后直接跳转对应页面,并return出来(防止下面可能会有其他跳转的场景)
   Taro.navigateTo({
     url: '/pages/goodsStore/goodsList/detail?goodsInfo=xxx',
   });
   return
   // 判断当前是否满足跳转活动商品支付页的条件
 } else if(scanType === 'activeGoodsDetail'){
   // 获取用户信息,这一步若是获取不到用户信息,会跳转到注册页面
   getUserInfo();
   Taro.navigateTo({
     url: '/pages/goodsStore/goodsList/detail/payPage?goodsInfo=xxx',
   });
   return
   // 判断当前是否满足跳转vip开通的条件
 } else if(scanType === 'openVip'){
   getUserInfo();
   Taro.navigateTo({
     url: '/pages/user/detail/vipPage?params=xxx',
   });
   return
   // 判断当前是否满足登录平板的条件
 } else if(scanType === 'loginPad'){
   getUserInfo();
   Taro.navigateTo({
     url: '/pages/device/login?deviceId=xxx',
   });
   return
 }
 // 其他逻辑代码...
}

如上代码所示,我们可以直接将getUserInfo()放在对应有需要的模块中去,这样就可以解决未注册用户进入注册页面的尴尬之处。

看着这“近乎完美”的代码,不由得欣慰的笑了笑,真好!

第四个需求

今天产品小姐姐又找我了,说有新需求了

  1. 我们要杀熟,用的多的用户进入“大怨种商品”页面,否则进入普通页面(咳咳,不对,是精准定位用户身份)
  2. 在扫码进入会员开通页面的时候,期望普通用户跳转进入的是会员业务介绍,会员用户进入的是超级会员的开通页面,超级会员进入的是会员续费页面。

看起来还挺绕是吧?于是我们小小的分析一波有了下面的理解,这两个实现方式都是一样的,那这边一个if...else,那边再加三个if...else,好家伙,快乐直接加倍!

上代码:

if(scanType === 'goodsDetail'){
   // 判断是否为老用户
   if(identity === 'oldUser'){
     Taro.navigateTo({
       url: '/pages/goodsStore/goodsList/detail/surprise?goodsInfo=xxx',
     });
   }else{
     Taro.navigateTo({
       url: '/pages/goodsStore/goodsList/detail?goodsInfo=xxx',
     });
   }
 
   return
   // 判断当前是否满足跳转活动商品支付页的条件
 } 
 ...
 else if(scanType === 'openVip'){
   getUserInfo();
   if(userIdentity === 'normal'){
     // 普通用户
     Taro.navigateTo({
     url: '/pages/user/detail/vipPage/normal?params=xxx',
   });
   }else if(userIdentity === 'vip'){
    // 普通会员
    Taro.navigateTo({
     url: '/pages/user/detail/vipPage/vip?params=xxx',
   });
   }else{
     // 超级会员
     Taro.navigateTo({
     url: '/pages/user/detail/vipPage/super?params=xxx',
   });
   }
   return
 }

轻轻松松需求又做完了。终于我们看着自己的代码感觉貌似没那么完美了,因为我的componentDidMount方法已经超过100行了,不由得怀疑人生,这就是我亲手制造出来的屎山嘛(成就感满满)。

好了,作为一个负责任的程序员(防止被同事骂),我看不下去了,我要改善它的逻辑!

重构阶段

现状

我们重新理性分析一波我们之前的代码,在首页中,我们所做的一些逻辑处理都混杂在一起,都是通过判断是某个功能模块后再进行相应的逻辑处理后再进入相应的页面,这样会造成我们每添加一些额外处理逻辑,就会导致首页代码的体量急剧膨胀,这显然不是好的实现方式。对于不熟悉的人以及日后的项目维护与扩展来说难度系数会很大。

其实不难发现目前的实现逻辑是如果判断满足当前条件就return,不再向下执行,否则继续向下执行直到有模块去处理该请求。

怎么样?有没有一点思路了,这让刚刚学习了一波设计模式的我蠢蠢欲动,因为这不就是责任链模式的使用场景嘛。因此我们就决定使用责任链模式去实现它了。既然这样我们就罗列一下我们现有的功能模块,将他们根据功能做好拆分。

功能模块罗列

  • 商品详情
  • 商品支付
  • 会员体系
  • 平板登录

将每个功能模块都单独封装起来,并且保证输出都为布尔值。

最终的实现效果图就是这样

image.png

这样的结构看起来很清晰有没有?下面是代码示例

componentDidMount(){
  // 模块需要使用的参数
  const params = {...};
  arrayExecuteFun([
    // 抽离出去的商品详情模块
    goodsDetailModule,
    // 抽离出去的商品支付模块
    goodsPayModule,
    抽离出去的会员体系模块
    vipModule,
    抽离出去的平板登录模块
    padLoginModule,
  ],
  params
 )
  // 其他逻辑代码...
}

// 控制方法,注意需要每个处理模块的最终输出都为布尔值
// taskList:任务列表
// params:执行参数
arrayExecuteFun(taskList, params){
   for (let index = 0; index < taskList.length; index++) {
      const result = await taskList[index](params);
      if (result) return;
    }
}

这样的话我们修改哪个模块只需要找对应模块去修改代码即可,不用在一堆if…else中去找逻辑了。方便你我他,再也不用担心被同事骂了;至此我们的屎山代码被我们成功优化啦,开不开心!

总结

基于上面的改善我们可以发现它的如下优点

1、可扩展性强,小程序中免不了日后会有较多的扫码进入指定页面的使用场景,只需要添加对应的模块就可以实现

2、可维护性好,由于每个模块之间相互独立,所以维护起来也很容易“对症下药”;

3、灵活性高,在处理较多场景时,我们可以通过责任链模式去组装起消息传递的通道,这让我们可以很灵活的去处理各种情况;

需要注意的是不同的组合顺序会导致不同的结果,链的组合很重要;

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容