## 《轻课》微信小程序踩坑历险记 我们在开发《轻课》小程序时,为了快速迭代和升级,全栈使用了前端技术,客户端采用微信小程序,后端采用 Node.js,整个技术栈上有利于前端团队快速迭代。 开始该项目前,我们已经做足了心里准备,知道必将面临不少挑战。但最终实践证明,我们所做的准备还是有些乐观了,实际遇到的挑战比我们预期的大不少。值得欣慰的是,最终的收获与挑战是成比例的。 本文将围绕开发过程中遇到挑战,及我们对此的思考与应对策略展开,希望也可以给大家带来收获。 #### 官方 IDE 不好用?我们来适当工程化小程序,并使用 VSCode 顺畅编码 小程序开发前,我们希望通过组件化方式进行更好的团队协作开发。虽然官方提供了 template,但使用该方法,依然无法彻底实现组件化开发。同时,我们也尝试了社区中几款不错的开发框架,但发现微信小程序官方对于这些框架一直都是不支持的态度,为了避免可能存在的风险,我们决定放弃使用这类框架,最终采用官方提供的 template 方案实现了部分的组件化,并在团队协作中,按页面单位对开发任务进行划分。 虽然微信开发者工具集成了小程序开发中需要用到的绝大多数功能,但在代码编辑体验上,并不能很好地满足我们的需求。目前市面上已经出现了几款提供小程序代码编译功能的其他 IDE 可供选择,比如白鹭的 Egret Wing,但为了及时更新小程序的 SDK,以及保留微信开发者工具集成的预览、显示后台配置等其它必要的功能,我们还是决定采用微信开发者工具+VSCode 的开发环境搭建方案,并采用 gulp,对代码进行简单的工程化。 同时,我们引入了 Less,将编译的最终 CSS 文件配置为 WXSS 文件,发现这在小程序中完全可行。此外,还增加了对 WXML、JSON 文件的编译,在微信开发者工具中,指定 gulp 生成的 dist 目录,通过常用的热更新功能,便实现了在 VSCode 中编写代码,更改可自动编译为可供微信开发者工具编译运行的小程序代码。代码一有改动,开发者工具指定目录的文件也会相应的被重新编译,开发者工具也会自动重新加载小程序预览,大大加快了编码效率,也方便通过 gulp 进一步拓展我们的编码能力,比如引入 Babel,我们实现了对一些 ES2017 语法的支持,特别是会让前端开发者两眼放光的 Async/Await 异步处理,使我们的小程序开发锦上添花。同时,让我们欣慰的是,小程序自身的异步 API 接口在使用 Await 调用时,表现良好。 #### 小程序动效性能优化 对于小程序的动效优化,实际上我们并没有办法像在 HTML5 中那样做太多的工作,因为小程序自身做了很多封装,也有一些限制。但令人欣慰的是,小程序本身在动效性能表现上虽然不能说特别超出我们的预期,但至少没有做得太差。唯一让我们遗憾的是,小程序自身并没有为我们处理任何 CSS3 动效的兼容问题,而由于小程序本身的限制,在兼容问题上,几乎束手无策。另一个令人难过的现实是,在小程序中,没有任何办法像在 HTML5 中那样,监听某个动效的结束事件,如 AnimationEnd,因此很难能像在 HTML5 中那样,游刃有余地实现很多复杂动效的组合。在这一点上,我们很无奈地采用了延迟一定时间间隔的方案,该方案几乎难以实现预想的动效组合,并且在各类设备中的效果差异也十分巨大。 因此在更复杂的动效中,我们采用了 SVG 的方案,然而让人绝望的是,小程序中使用 SVG,本身也是有兼容问题的,于是也只能通过调整 SVG 中实现的参数甚至方案来尝试解决各类设备的兼容问题,这种调试体验对于开发者而言,是不堪回忆的。 这次开发的小程序有着非常丰富的交互体验。而最终,我们得到的经验,却是不要尝试在小程序中实现复杂的动效,以及复杂的交互,否则成本投入会远大于预期。这其实是小程序开发中,比较令人失望的一点,得到了最深的教训。小程序的应用场景是简单、用完即走,但让我们没想到的是,连动效也是“动完即走”,官方文档也并没有告诉我们“不要尝试实现复杂的动效和交互”。 #### 小程序无法获取录制音频的长度?对于程序逻辑的影响及处理思路 看到这里,可能读者会反问我,API 接口提供了方法获取音频的长度了呀?按照我们的实践,小程序本身不是一个 Native,却要做 Native 事的一个框架。做 Native 的愿景和目标,我们是欣赏的,但是对于实现的调用原生功能的性能,我们还是感到很遗憾。 我们的产品为了增加趣味性,以及评测功能的实用性,涉及了很多音效播放,以及列队播放音频的逻辑。 通过各种踩坑,总结出稳定实现这个功能最好是用小程序提供的 wx.playBackgroundAudio 及相关的音乐播放控制接口,因为其调用的是微信自己的音乐播放组件,其设备兼容性最好,也能实现音效的异步播放,相对来说不影响界面交互。但是,该接口也不是银弹,很快发现有一部分音频,这个接口并不支持播放。这些是短于15s的音频,它们在某些设备里无法正常播放,虽显示播放成功,但却没声音。这时,我们非常痛心地采用了会逼死处女座的解决方案,在短音效音频末尾加上5s左右的空白音频,来解决这个问题。虽然此法非常难以说服我们自己,但最终使我们至少从这个奇怪的设备兼容旋涡里走出来。 那么接下来,则是更为可怕的实现列队播放音频了。可能读者又会说,“切,监听 wx.onBackgroundAudioStop 不就可以了”,Too young ,Too simple。抛开我们写的测试方法在有些设备上不会百分百的执行 wx.onBackgroundAudioStop 回调不说,某些音频是用户录制的,也就是通过 wx.startRecord 接口完成录音,小程序录音后的文件格式为 silk,让我们没有想到的是,小程序的 wx.playBackgroundAudio 根本无法播放自己的录音文件 silk,因此通过 wx.onBackgroundAudioStop 回调来监听播放完成,是无解的。我们最终的实现方案,则是通过后端接口来返回语音录制的时长。后来也尝试了通过计时录音时长来实现,但无论如何,都不是我们理想的方案。在实现过程中,还会有部分设备通过曲线方案得到的录音时长不准确的问题,我们最终又是通过增加一定的“阈值”来尽可能规避,依然是一种虐心的解决方案。 #### 小程序真的能从本地读取音频资源吗? 答案是,并不能。 我们在开发这个产品之前,都认为小程序作为 Native 理念的践行者,理应可以实现直接读取本地音频资源,但事实证明,我们想多了。小程序提供的接口只能播放网络资源,本地资源是无法播放的。OK,我们需要面对现实,但是,我们的小程序产品是有一些音效需要保证即时播放的,从网络加载无法满足这个要求,那么既然无法从本地加载和播放音频,那至少我们提前从网络下载到本地并播放,是可以的吧,不然为什么要提供 wx.downloadFile 接口呢?这样在产品一开始提示一下“加载中”,也是一个比较好的方案。事实证明,我们依然想多了,这个也行不通,因此,最终我们采用的方案就是,没有方案。用户第一次访问音效时,只能接受无法意料的网络延迟这一现实了。 #### 小程序的周期回调 onReady 和 onShow 等真的靠谱吗? 小程序提供的生命周期回调,是我们很多产品逻辑的基础,但是这些生命周期函数真的靠谱吗?答案是,不一定。 不过读者先别惊,这里不是说这几个周期回调存在不被调用的情况,相反,它们是一定会被调用的。但由于小程序是做 Native 的非 Native 框架,所以期望的执行逻辑,并不一定会如我们所期望的那样执行。例如,如果在 onShow 中调用了 wx.playBackgroundAudio 来播放音乐,而在 onHide 中调用 wx.stopBackgroundAudio 来停止播放。那么这个过程可能并不会像预期的那样顺利。onShow 中的 wx.playBackgroundAudio 实现调用微信原生的音乐播放功能,所以需要启动时间,而如果用户在这个时间内离开小程序,虽然会触发 onHide 事件,并调用 wx.stopBackgroundAudio 接口,但由于音乐并未开始播放,该接口的调用是无效的。于是,神奇的现象出现了,用户返回微信聊天列表后,音乐又神奇的开始播放了,并未受 onHide 中 wx.stopBackgroundAudio 接口的调用影响。而这个情形,测试妹子们会无情的标记为 Bug,而我们却很难说服产品接受这个现实。 #### 微信小程序的未来 虽然以上所述,似乎都在吐槽小程序。但留心观察,我们不难发现,目前微信小程序在市场中越来越火爆了,而微信也开始持越来越开放的态度,不得不承认现在的微信小程序有诸多的问题,但优点也是显而易见的。更便利的入口,更低的部署成本,加之越来越被用户认可,我们相信这些问题也会很快逐步解决。更何况,光是微信这个平台,纵有千万般困难,我们开发者能 Say No 吗? 只是,在选择微信小程序应用场景这件事上,我还是建议读者朋友们,请尽可能地简化你们的产品原型,选择最简化的核心功能,再选择用微信小程序来实现,才是一个正确的姿势。 >
沙拉依丁·苏里坦
>《轻课》前端架构师,负责前端架构的优化及新业务拓展。《轻课》获首批小程序内测资格后,主导开发了多个小程序 Demo 及深入实践。曾在乐视担任前端工程师一职并带领团队完成《乐嗨》移动前端开发等任务。熟悉 Java、C#、PHP 开发,拥有丰富的全栈开发经验。