- 教你解决showLoading 和 showToast显示异常的问题
问题描述 当wx.showLoading 和 wx.showToast 混合使用时,showLoading和showToast会相互覆盖对方,调用hideLoading时也会将toast内容进行隐藏。 触发场景 当我们给一个网络请求增加Loading态时,如果同时存在多个请求(A和B),如果A请求失败需要将错误信息以Toast形式展示,B请求完成后又调用了wx.hideLoading来结束Loading态,此时Toast也会立即消失,不符合展示一段时间后再隐藏的预期。 解决思路 这个问题的出现,其实是因为小程序将Toast和Loading放到同一层渲染引起的,而且缺乏一个优先级判断,也没有提供Toast、Loading是否正在显示的接口供业务侧判断。所以实现的方案是我们自己实现这套逻辑,可以使用Object.defineProperty方法重新定义原生API,业务使用方式不需要任何修改。 代码参考 [代码]// 注意此代码应该在调用原生api之前执行 let isShowLoading = false; let isShowToast = false; const { showLoading, hideLoading, showToast, hideToast } = wx; Object.defineProperty(wx, 'showLoading', { configurable: true, // 是否可以配置 enumerable: true, // 是否可迭代 writable: true, // 是否可重写 value(...param) { if (isShowToast) { // Toast优先级更高 return; } isShowLoading = true; console.log('--------showLoading--------') return showLoading.apply(this, param); // 原样移交函数参数和this } }); Object.defineProperty(wx, 'hideLoading', { configurable: true, // 是否可以配置 enumerable: true, // 是否可迭代 writable: true, // 是否可重写 value(...param) { if (isShowToast) { // Toast优先级更高 return; } isShowLoading = false; console.log('--------hideLoading--------') return hideLoading.apply(this, param); // 原样移交函数参数和this } }); Object.defineProperty(wx, 'showToast', { configurable: true, // 是否可以配置 enumerable: true, // 是否可迭代 writable: true, // 是否可重写 value(...param) { if (isShowLoading) { // Toast优先级更高 wx.hideLoading(); } isShowToast = true; console.error('--------showToast--------') return showToast.apply(this, param); // 原样移交函数参数和this } }); Object.defineProperty(wx, 'hideToast', { configurable: true, // 是否可以配置 enumerable: true, // 是否可迭代 writable: true, // 是否可重写 value(...param) { isShowToast = false; console.error('--------hideToast--------') return hideToast.apply(this, param); // 原样移交函数参数和this } }); [代码] 调整后展示逻辑为: 优先级:Toast>Loading,如果Toast正在显示,调用showLoading、hideLoading将无效 调用showToast时,如果Loading正在显示,则先调用 wx.hideLoading 隐藏Loading
2019-10-30 - 微信小程序答题页——swiper渲染优化及swiper分页实现
前言 swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案 这里实现了如下功能和细节: 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验示例动图 [图片] 截图 [图片] [图片] 问题原因 当swiper-item数量很多的时候,会出现性能问题 我实现了一个答题小程序,在一次性加载100个swipe-item的时候,低端手机页面渲染时间达到了2000多ms 也就是说在进入答题页的时候,会卡顿2秒多去加载这100个swiper-item 思考问题 那我们能不能让他先加载一部分,然后滑动以后再去改变item的数据,让swiper一直保持一定量的swiper-item? 注意到官方文档有这么两个属性可以利用,我们可以开启衔接滑动,然后再bindchange方法中去修改data [图片] 1、保证swiper-item的数量固定,加载大量数据时,优化渲染效率 假设我们请求到的数据的为list,实际渲染的数据为swiperList 我们现在给他就固定3个swiper-item,前后滑动的时候去替换数据 正向滑动的时候去替换滑动后的下一页数据,反向滑动的时候去替换滑动后的上一页数据 当我们知道了要替换的条件,我们便可以去替换数据了 但是我们应该考虑到临界值的问题,如果当前页是list第一项和最后一项该怎么办,向左向右滑是不是得禁止啊 这边是判断没数据会让它再弹回去 2、记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页 有很多时候,我们是从某一项直接进来的,比如说上次答题答到了第五题,我这次进来要直接做第六题 那么我们需要去初始化这个swiperList,让它当前页、上一页、下一页都有数据 3、答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验 从答题卡选择index,那就不仅仅是滑动上下页了,它可以跳转到任何页,所以也采用类似初始化swiperList的方法 swiper切换动画我这边是默认250ms,但是发现有时候从答题卡点击回来,你在答题卡点击的下一项不知道会从左还是从右滑过来 体验真的很差,一开始不知道怎么禁掉动画,其实在跳转到答题卡页的时候把duration设为0就可以了 然后在答题卡页的unload方法中恢复 关键点: 在固定3个swiper-item的同时,要保证我们可以有办法来替代微信自带swiper的current属性和change方法 swiper-limited-load使用方法及说明: 将components中的swiper-limited-load复制到您的项目中在需要的页面引用此组件,并且创建自己的自定义组件item-view在初始化数据时,为你的list的每一项指定index属性具体可以参照项目目录start-swiper-limited-load中的用法说明:其它属性和swiper无异,你们可以自己单独添加你们需要的属性总结 一开始很头疼,为什么微信小程序提供的这个swiper,没去考虑这方面 然后在网上和社区找也没有一个特别好的解决方案。 后来想想,遇到需求就静下来解决吧。 项目地址:https://github.com/pengboboer/swiper-limited-load 如果错误,欢迎指出。 如有新的需求也可以提出来,如果有时间的话,我会帮你们完善。 如果能帮到你们,记得给一个star,谢谢。 ---补充 有很多朋友在评论区提到了分页的需求,抽时间写了一个分页的Demo和大家分享一下。 还是以答题为例,比如我们一共有500条数据,一页20条,可能需要如下功能,乍一看不就加了个分页,挺简单的,其实实现起来挺麻烦的,下面说一下思路和一些需要特别注意的点: 1、从其他页面跳转到答题页时,不光只能默认在第一题,可以是任意一题,比如第80题。 跳转到任意一题,那么需要我们根据index算出该数据在第几页,然后需要请求该页数据,最后显示对应的index。我的思路更注重用户体验,不可能是上滑或者下滑才开始去请求数据,一定是要用户滑动前提前请求好数据。所以起码要保证左右两侧在初始化那一刻都有数据。如果此题和它的上一题下一题都在同一页,那么我们只需要请求一页数据(第15题,那么只需请求第1页数据)。如果此题和它的上一题或者下一题不在同一页,那么我们可能需要请求两页数据。(第20题,那么需要请求第1页和第2页数据) 2、左滑、右滑没数据时,都可以加载新数据。直到滑到第一题或者最后一题。 如果我们初始化时是第24题,那么我们左滑到第21题时,就应该去请求第一页的数据。那么用户在看完21题时,再滑到20题,可能就根本不会感知到通过网络请求了数据。但是如果用户此刻滑动特别快:滑到21题时请求了网络,请求还没成功,就又向左滑了。那么我们需要限制用户的滑动,给用户一个提示:数据正在加载中。 3、从答题卡点击任意一题可以跳转到相应的题目,并且左右滑动显示正常数据 比如我们初始化是跳转到了第80题,不一会点击答题卡又要跳转到200题,一会又跳转到150题。各种无序操作,你也不知道用户要往哪里点。 一开始是想着维护一个主list,点到哪道题往list中添加这道题所在的当页的数据,但是还得判断这一页或者左滑右滑请求新一页的数据得往list的哪个位置添加。这来回来去乱七八糟的判断就很麻烦了,很容易出bug。而且list长度太长了以后insert的性能也不好。 后来就去想,要不答题卡点击任意一题都清空旧的list,然后请求新的数据,左右滑动没数据了再请求新的数据呗。但是这样很浪费资源,并且用户体验也不好,用户已经从第1题答到第200题了,这时用户从答题卡选择了一个25题,还得重新请求网络。而且200道题的数据都没了,那再选个26题,再重新请求网络?网络有延时不说,还浪费资源。 最后转念一想,这时候就需要弄一个缓存了。所以最终的解决方法就出来了:我们维护一个map,在网络请求成功后,在map中保存对应页的数据,同时我们维护一个主list来显示对应的题目。当我们在答题卡选择某一题目,就清空list,然后判断map中有没有该页的数据,如果有就直接拿来,没有就再去网络请求。这个处理方式,写法相对来说简单,不需要乱七八糟的判断,也不浪费资源,用户体验也很不错。 总结 以上就是一些思路和要注意的地方。这个Demo断断续续花了好几天时间写出来的。可能我说的比较啰嗦比较细,只是想让需要用到这个分页Demo的同学能理解我是如何实现的。 如果觉得能帮到你,记得给一个star,谢谢。同时如果这个demo有bug或者你们有新想法,欢迎提出来。
2021-01-07 - 已解决。小程序获取手机号时,checkSession通过但是获取手机号解密失败
一开始我的处理方式是在页面直接用checkSession,我的session_key是在index.js登录的时候保存到storage,这里check回调的是“success”。 但是把此时storage里面的session_key结合授权按钮的参数去进行解密是失败的,需要在当前的Page再登陆一次才能成功。 不推荐把session_key存放在缓存。所以以上做法直接跳过。 最后参考了一个朋友的做法,在Page onLoad的时候执行一次wx.login(),然后拿到新的session_key,再用此时的新key去解密就通了。或者改为请求解密之前执行一次登录,据说出问题的概率还是很大 结尾补充:最后一种方法还有个问题要考虑,就是最好执行获取手机号之前再checkSession一下(尽管没啥用)。 问题源头,由于这个函数在校验session_key的时候,无论是过期的key还是新的key都是success,所以有了之后一些列的问题,session_key的状态没法把控 [代码]Page({ data: { currentSessionKey: null }, onLoad: function(options) { /* do something*/ const here = this; // 执行登录确保session_key在线 wx.login({ success(res) { if (res.code) { // call()是我自己基于wx.request封装的一个请求函数工具,这里通过后端发送登录请求获得openid const data = call(userLogin, { code: res.code }); data.then(obj => { if (!obj.error) { here.setData({ currentSessionKey: obj.result.session_key }) } }); } }, fail(error) { throw error; } }); }, // 点击按钮获取手机号权限并解析<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" bindtap='doMyAction'>获取手机号</button> getPhoneNumber: function (e) { const { encryptedData, iv } = e.detail; const options = { encryptedData: encryptedData, iv: iv, sessionKey: this.data.currentSessionKey }; here.doGetPhone(options); }, doMyAction: function() { // 还可以做一些事情 }, doGetPhone: function (options) { const { sessionKey, encryptedData, iv } = options; const here = this; // 向服务器请求解密 wx.request({ // 这里是解密用的接口 url: 'https://xxx.com/python/decrypt', method: 'POST', data: { sessionKey: sessionKey, encryptedData: encryptedData, iv: iv }, success(res) { // 最终获取到用户数据,国家代号前缀、不带前缀的手机号。默认是不带前缀 const { countryCode, purePhoneNumber } = res.data; here.pageForward(countryCode, purePhoneNumber); }, fail(error) { console.log(error); here.pageForward(); } }) }, pageForward: function(countryCode, purePhoneNumber) { // 获取成功后我是跳转到另一个页面 wx.navigateTo({ url: `/pages/person/index?phone=${purePhoneNumber}` }) } }) [代码]
2020-09-15 - 各种路由情况下使用 getCurrentPages 可能存在的陷阱
废话不多说,直接上微信API。 PageObject[] getCurrentPages()获取当前页面栈。数组中第一个元素为首页,最后一个元素为当前页面。 注意: 不要尝试修改页面栈,会导致路由以及页面状态错误。 不要在 [代码]App.onLaunch[代码] 的时候调用 [代码]getCurrentPages[代码],此时 [代码]page[代码] 还没有生成。 这是微信官方对 getCurrentPages 做的解析和使用注意点,相信大家都很熟悉了。文档中明确指出 不要在 [代码]App.onLaunch[代码] 的时候调用 [代码]getCurrentPages[代码],此时 [代码]page[代码] 还没有生成。 那么是不是只要我们不再 App.onLaunch 中使用,getCurrentPages 就会按照我们的预期来工作呢?其实并不尽然。 我在这里为了演示,写了两个页面 First Page 和 Second Page。First Page 可以跳转到 Second Page , Second Page 也可以回退到 First Page. [图片][图片][图片][图片] 并且在两个页面的 onShow 方法中调用了 [代码]getCurrentPages[代码] 方法。 onShow(){ let pages = getCurrentPages(); console.log('------------------- First Page onShow方法中获取栈内页面'); console.log(pages); }, 重新运行小程序,从 First Page 跳转到 Second Page ,观察控制台日志 [图片][图片] 可以看到在 First Page 的 onShow 方法中打印的包含一个对象H的数组, 在 Second Page 的 onShow 方法中打印出了包含两个对象H的数组,H 可以理解为Page实例对象的名称。这和我们预期的是一样的。 接下来我们举个特别点的栗子,怎么特别呢?这回我们的栗子没用糖炒,炒栗子的伙计把“路由”错当成糖了。那我们就来看看当栗子,啊...,不,应该是 [代码]getCurrentPages[代码] 遇到“路由”后会变成什么味儿呢? 首先我们来看看微信小程序API中都有哪些路由 [图片][图片] 没错就是这几个控制小程序页面跳转的全局函数。我们在这里尝试一下最常用的两个 navigateTo 和 navigateBack,其他的同理。 下面我们来看看 First Page 的跳转方法代码: /** * 跳转到 Second Page */ navigateToSecondPage() { wx.navigateTo({ url: '/other/other', }) let pages = getCurrentPages(); console.log('------------------- First Page navigateToSecondPage方法中获取栈内页面'); console.log(pages); }, 我们重新运行一下小程序后点击 First Page 中 跳转到Second Page的按钮,观察一下控制台的日志输出情况 [图片][图片] 哎哎哎......?怎么哪里好像不对呢?炒栗子的伙计说在点击按钮的时候我明明往锅里加了一个 Page 实例啊,为什么我放进去后没有马上看见呢?完了这下炒出来的栗子又变味了。 我们知道 navigateTo 方法肯定是向页面栈里又加入了一个新页面实例的,那么我们去 Second Page 中的 onLoad 方法中看看,有几个 Page 实例: [图片][图片] 哎呦,在Second Page 中明明就是两个,怎么在跳转执行后没有看到呢?伙计想是不是自己太心急了,于是他在加入第二个Page后并没有马上去看而是出去抽了根烟。 /** * 跳转到 Second Page */ navigateToSecondPage() { wx.navigateTo({ url: '/other/other', }) let timerId = setTimeout(function(){ let pages = getCurrentPages(); console.log('------------------- First Page navigateToSecondPage 1S later 方法中获取栈内页面'); console.log(pages); }, 1000); }, 抽完烟后回来发现又正常了 [图片][图片] 点击完跳转按钮1S后,第二个Page实例奇迹般地出现了。伙计有点兴奋啊。于是决定了以后碰到 [代码]getCurrentPages[代码] 和 路由就出去抽根烟,啊...... ,不不不,是等会再看。可是这并不是万全之策。我们都知道 setTimeOut 的运行机制,它是在当前任务栈内的任务执行完毕后再执行 setTimeOut 里任务。这个任务的执行开始时间是当前任务栈内所有的任务执行完成的耗时 + setTimeOut 的延迟时间,也就是说是一个不确定的时间,换个说法就是伙计出去抽根烟回来不一定能看见第二个Page实例。总感觉不是那么安全,那怎么办呢? 我们仔细阅读 navigateTo 的使用文档就会发现,它除了可以指定跳转路径(url)外,还可以添加三个回调方法。 [图片][图片] 那么现在我们根据使用文档来试着修改一下我们查看 page 实例的时间。我们等到跳转成功后就立马去查看。 /** * 跳转到 Second Page */ navigateToSecondPage() { wx.navigateTo({ url: '/other/other', success: function(){ let pages = getCurrentPages(); console.log('------------------- First Page navigateTo Success 方法中获取栈内页面'); console.log(pages); } }) }, 重新运行一下程序: [图片][图片] 好像这个success回调也不是很靠谱,还是得等一小会儿才能看见第二个Page实例。 这是 navigateTo ,下面我们来看看 navigateBack 。 navigateBack(){ let pages = getCurrentPages(); console.log('------------------- Second Page navigateBack 执行之前获取栈内页面'); console.log(pages); if(pages.length > 1){ wx.navigateBack({ delta: 1, success: function(){ console.log('-------------------Second Page navigateBack Success获取栈内页面'); let pages1 = getCurrentPages(); console.log(pages1); } }); } }, 我们清空控制台日志,然后点击 Second Page 的 返回 First Page 按钮 [图片][图片] 我们可以看到 wx.navigateBack 的 success 函数中可以拿到准备的页面堆栈数据。当然你也可以出去抽根烟等会回来再看数据。 所以我们根据上面的Demo可以得出两个小结论: 1 wx.navigateTo 执行之后只能通过延时的方法去获取准确的页面堆栈数据,具体延时多少看你炒栗子的经验喽。 2 wx.navigateBack 执行后可以通过延时和回调方法进行获取页面堆栈数据。 至于其他的路由的情况就不啰嗦那么多了,结论如下: 3 wx.switchTab 执行后可以通过延时和回调方法进行获取页面堆栈数据, 同 wx.navigateBack。 4 wx.reLaunch 执行之后只能通过延时的方法去获取准确的页面堆栈数据, 同 wx.navigateTo。 5 wx.redirectTo 没有办法在执行wx.redirectTo的页面内获取准确的页面堆栈数据。 怎么样,有没有感觉很 因吹丝汀 啊..... 好了,大概就这些了。 重要的事情说三遍: 尽量不要在执行完路由函数后立即调用 getCurrentPages 函数! 尽量不要在执行完路由函数后立即调用 getCurrentPages 函数! 尽量不要在执行完路由函数后立即调用 getCurrentPages 函数! 不然你会吃到怪味的栗子,希望可以帮到大家,如果哪些地方写的不对或不够准确,请大家批评指正。
2019-12-12 - 微信小程序设置过滤器将时间戳转化为日期字符串
在微信开发小程序时,后台传入的日期数据可能是时间戳 而不是日期 , 或者需要把日期转换成时间戳来做出相应的处理时,我们将用到时间戳和日期的相互转换. WXS是专供WXML调用的有独立作用域的JS模块(不是全功能的JS,感觉有所限制),可以在在视图层对数据进行格式化处理,起到过滤器的作用。 js文件 Page({ data: { timestamp: 1522117395730 }}) wxml文件wiz_code_mirror>/wiz_code_mirror> <wxs module="m1"> function format(ts) { var d = getDate(ts) return [d.getFullYear(), d.getMonth()+1, d.getDate()].join('-') + ' ' + [d.getHours(), d.getMinutes(), d.getSeconds()].join(':') } module.exports.format = format</wxs><view>{{ timestamp }}</view><view>{{ m1.format(timestamp) }}</view> 注意:wxs 获取当前日期不可以使用 new Date(),而需要使用 getDate() 代替。 wiz_tmp_tag style="display: none;">/wiz_tmp_tag> 更多分享请移步我的个人博客: https://tianxintiandisheng.github.io/
2018-08-28 - Expected updated data but get first ?
最近突然出现Expected updated data but get first rendering data报错,没有具体位置信息,无法提供相关的代码片段,这个问题如何解决,测试过两个空白页跳转也会出现此问题
2019-09-06 - 2019-03-12