艺灵设计

全部文章
×

uni-app跨端开发微信小程序之动态配置自定义tabBar那点儿坑

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2021-05-31 21:09:03 - 阅: - 评:0 - 积分:0

摘要:微信小程序原生的tabBar虽丝滑顺畅,但无法做到动态增删,只能通过几个API做简单的修改,很难满足实际的项目需求。但自定义的tabBar虽灵活,但在用户体验上始终会有所欠缺。不是随页面飘动就是在页面切换时会闪一下,而使用tab的方式虽可以做到无闪动,但部分生命周期函数又无法触发......

接上一篇文章,虽然我们可以在打包前动态生成微信小程序配置文件,也可以动态修改底部导航栏数据。但是,项目一经打包发版后,我们便无法对底部tabBar进行任何操控了。对于saas或一些装修平台来说,这是无法接受的!毕竟用户会随时有修改底部导航栏的需求,如果每次都要发布过审,那这个平台还玩个锤子呀![:笑哭]

那有没有解决方案呢?

答案是肯定的,只不过自定义的tabBar没有微信小程序自带的丝滑顺畅。不信你去看微盟、有赞、凡科等这些大平台上提供的微信小程序案例。在切换页面时,底部导航栏tabBar不是飘过就是闪一下,始终不会像微信原生小程序那样一直固定在底部。

这种现象在苹果手机上差别不大,但在不同的安卓手机上,那差距可就大的去了。有兴趣的看官可以前往上述平台,随便找几个案例用手机扫码体验便是。

一、一图知现状

话不多说,开始今天的内容吧!先来一张思维导图镇楼,如图:微信小程序底部导航栏tabBar.png 微信小程序底部导航栏tabBar

虽然上面这张图总结的并不完整,但这确实是很多开发者要面临的现状。通常情况下我们都会使用原生的导航,因为自定义的tabBar吃力不讨好。除非是一些特殊需求,否则不建议看官折腾,毕竟踩坑加班是要自己扛的哈!

这篇文章,艺灵我主要想谈谈图中右侧这一块儿,同时我也会附上几个案例做演示。源码会在文章末尾提供,有兴趣的看官可以前往github进行下载。

二、灵魂三问

2.1、为什么我们自定义的tabBar会飘动而微信官方自带的不会?

这个问题问的好,要想知道为什么飘动,首先我们得知道微信小程序页面是如何跳转的。为了让看官更好的了解问题,飘动现象见下方视频,视频底部的导航栏才是重点哈!视频如下↓↓↓:点击底部导航栏中菜单时底部导航栏会跟随页面飘动.mp4 使用wx.navigateTo跳转页面底部导航栏会飘动(友情提示:点击上面的图片即可播放视频)

上面的视频比较短,只有1秒,建议看官仔细看底部的导航栏。你会发现在页面切换时,他会从右向左滑出。虽然速度很快,但还是能看到明显的飘动现象。

页面跳转,这个需求如果放到pc端或h5端,最简单的就是直接一个href="xxx"完事儿了。如果是用js控制,那也是一个location.href="xxx"的事情。同理,回到微信小程序这边,对于页面跳转,微信官方提供了5种方式。对,你没有听错,是5!种!方!式!有兴趣的可以戳我访问官方文档,没兴趣的继续往下看。五种跳转方式如下:

// navigator的open-type 的合法值

navigate // 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。对应 wx.navigateTo 或 wx.navigateToMiniProgram 的功能

redirect // 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。对应 wx.redirectTo 的功能

switchTab // 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。对应 wx.switchTab 的功能

reLaunch // 关闭所有页面,打开到应用内的某个页面。对应 wx.reLaunch 的功能 1.1.0

navigateBack // 关闭当前页面,返回上一页面或多级页面。对应 wx.navigateBack 的功能

wx.navigateTo这种最常见,其效果就是页面从右向左进入。此时看官是不是察觉到了什么?是的,上面提到的飘动问题正是跟这个有关。

2.1.1、解决方案

由于我们要模拟tabBar的功能,并且微信小程序页面栈有十层的限制,所以这里我们只能选择redirectreLaunch

2.2、为什么我们自定义的tabBar会闪动?

如果看官尝试了上面的API,那就要恭喜看官了,您虽然解决了底部导航栏飘动的问题,但现在他会闪一下呀!视频演示:点击底部导航栏中的菜单时底部会闪一下.mp4 优化后底部导航栏会闪动(友情提示:点击上面的图片即可播放视频)

重点同样是底部的导航栏,虽然现在不会飘的一下就出现了,但闪一下还是不太美观。

那有没有好点儿的补救方案呢?

2.2.1、页面加loading等待动画

啥意思呢?就是搞一个全屏的加载动画并设置个背景色挡住原始页面,当页面完全加载后延时隐藏这个遮罩层。虽然这样做看上去用户体验会稍微好一点儿,但要命的是每次进入页面时都会先出现loading等待,个人觉得用户体验会更差。

上面提到的相关解决方案,即使看官使用了vuex来做状态存储也仍无法从根源解决问题。

2.3、飘动闪动都不要,我就要让他固定不动不行吗?

行,当然行啊!还记得tab选项卡功能的实现原理吗?

如果看官想到了这一点儿,在某种程度上来说,确实是一大进步。使用选项卡布局有两种加载方式,一种是在页面上把要展示和几个页面全部加载,这种方法的弊端就是当前页面体积过大且存在太多的请求。另一种是用v-if来控制,好处就是初始化时不会加载所有页面,大大减少了请求和页面体积。

那使用v-if的方式是不是就完美呢?

2.3.1、部分生命周期函数无法触发

显然不是的,因为使用v-if控制组件加载,有些生命周期函数是不会执行的。如图:用v-if控制组件会导致部分生命周期函数无法触发.gif 用v-if控制组件会导致部分生命周期函数无法触发

而实际上,每个页面我都打印了很多生命周期函数,比如:

// 页面生命周期
onLoad(e) { // 页面创建时执行
  console.log('index 页面 onLoad', e, Date.now());
},
onShow() { // 页面出现在前台时执行
  console.log('index 页面 onShow', Date.now());
},
onReady() { // 页面首次渲染完毕时执行
  console.log('index 页面 onReady', Date.now());
},
onUnload() { // 页面销毁时执行
  console.log('index 页面 onUnload', Date.now());
},
onHide() { // 页面从前台变为后台时执行
  console.log('index 页面 onHide', Date.now());
},
created() { // 在实例创建完成后被立即调用
  console.log('index 页面 created', Date.now());
},
mounted() { // 挂载完成(可以访问DOM元素)
  console.log('index 页面 mounted', Date.now());
},
updated() { // 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
  console.log('index页面 updated', Date.now());
},

正常情况下,在首次进入页面时,触发的生命周期函数会多些。如图:正常切换页面时触发的生命周期函数.png正常切换页面时触发的生命周期函数

所以,如果原页面在onLoadonShow中写有代码的话,在使用v-if时就需要注意了,需要更换可触发的生命周期函数,放到createdmountedonReady中,以避免页面加载后数据无法展现。

除此之外,如果有些页面需要做上拉加载、下拉刷新的话,也同样需要手动做些处理。示例如下:

// 通过selectComponent来调用组件中的方法
<template>
  <view class="content">
    <div v-if="current === 0"><indexTem id="indexTem"></indexTem></div>
    <div v-else-if="current === 1"><jsTem id="jsTem"></jsTem></div>
    <div v-else-if="current === 2"><templateTem id="templateTem"></templateTem></div>
    <u-tabbar :list="tabbar" :current="current" :mid-button="true" @change="change"></u-tabbar>
  </view>
</template>

<script>
import indexTem from '../index/index';
import jsTem from '../js/index';
import templateTem from '../template/index';

export default {
  data() {
    return {
      title: '模板',
      tabbar: [],
      current: 0
    };
  },
  components: { indexTem, jsTem, templateTem },
  onLoad() {
    this.tabbar = [
      {
        iconPath: '/static/uview/example/component.png',
        selectedIconPath: '/static/uview/example/component_select.png',
        text: '组件',
        count: 2,
        isDot: true,
        pagePath: '/pages/index/index',
        name: 'indexTem'
      },
      {
        iconPath: '/static/uview/example/js.png',
        selectedIconPath: '/static/uview/example/js_select.png',
        text: '工具',
        midButton: true,
        pagePath: '/pages/js/index',
        name: 'jsTem'
      },
      {
        iconPath: '/static/uview/example/template.png',
        selectedIconPath: '/static/uview/example/template_select.png',
        text: '模板',
        pagePath: '/pages/template/index',
        name: 'templateTem'
      }
    ];
    console.log('tab切换页面 onLoad', Date.now());
  },
  onShow() {
    console.log('tab页面 onShow', Date.now());
  },
  created() {
    console.log('tab页面 created', Date.now());
  },
  onHide() {
    console.log('tab页面 onHide', Date.now());
  },
  updated() {
    console.log('tab页面 updated', Date.now());
  },
  mounted() {
    console.log('tab页面 mounted', Date.now());
  }, // 生命周期-挂载完成(可以访问DOM元素)
  onReady() {
    console.log('tab页面 onReady', Date.now());
  }, // 生命周期回调—监听页面初次渲染完成
  onUnload() {
    console.log('tab页面 onUnload', Date.now());
  }, // 生命周期回调—监听页面卸载
  onPullDownRefresh: function() {
    // 触发下拉刷新时执行
    console.log('tab页面 onPullDownRefresh', Date.now());
    this.componentName = this.tabbar[this.current].name;
    this.$nextTick(() => {
      // 获取组件实例【重点!!!】
      const selectComponentName = this.selectComponent('#' + this.componentName);
      if (selectComponentName) {
        selectComponentName.onPullDownRefresh && selectComponentName.onPullDownRefresh();
      }
    });
  },
  onReachBottom: function() {
    // 页面触底时执行
    console.log('tab页面 onReachBottom', Date.now());
  },
  onShareAppMessage: function() {
    // 页面被用户分享时执行
    console.log('tab页面 onShareAppMessage', Date.now());
  },
  onPageScroll: function() {
    // 页面滚动时执行
    console.log('tab页面 onPageScroll', Date.now());
  },
  methods: {
    change(val) {
      this.current = val;
    }
  }
};
</script>

上面的示例代码中,我们可以通过selectComponent这个API来获取组件的实例,从而调用组件中的方法来实现下拉刷新,想调用其他生命周期相关的函数同理。

三、我该怎么办?

前面说了这么多,说到底,想要完美,至少目前是不可能的!虽然tab的方式无闪现,但无法把控哪些页面需要做成伪tabBar。如果采用遍历的方式,tab页又比较大且可能会存在更严重的问题。现在再想想微盟、有赞、凡科这些大平台做的案例,也就能够理解了。

四、源码下载

为了方便看官研究代码,艺灵已经将相关文件打包到了网站上,有需要的看官可以点击右侧链接进行免费下载→→
本站下载:[源码]uni-app跨端开发微信小程序之自定义TabBar.zip
github下载:分支:dev-uni-app-custom-tabBar-20210531

五、最后

上面的案例中使用了uView这个第三方插件,如果看官使用uni-app开发微信小程序的话,艺灵强烈推荐uView的哈!别问,问就是有些功能节省开发时间!戳我访问uview ui 官方文档

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:/xwzj/2021-05-31/uni-app-custom-tabBar.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: uni-app 跨端开发 微信小程序 tabBar saas redirect switchTab selectComponent uview HBuilder

上一篇: uni-app跨端开发微信小程序之nodejs与后端通信并动态打包项目以适应多环境开发   下一篇: @vue/cli3项目开发之记一次手动配置前端一键自动部署代码到服务器上的经历

评论区