小程序开发的一些经验

本文并非入门向介绍文章,适合有一定前端基础的开发者阅读。

经过年前若干天的开发、测试、审核,我司的小程序上线了,这是一个专注与打通线下体验、线上购买流程的小应用,感兴趣的可以微信内搜索 “ 发现匠物” 进行体验。

开发整体体验

首先我们回忆下最开始公测时官方开发工具是个什么样子:

  • 每次编辑完需要点击编译,偶尔还需要再刷新,因为刷新完总回到首页,所以调试特定页面需要每次重新导航
  • 编译出错或者运行出错看不到提示,开发者只能对着白屏进行各种尝试性修复
  • 经常性崩溃,高 CPU 高内存占用问题频发

前两个问题开发工具已经很好解决了,最后的问题在正式版发布后我也没再碰到过了,可以说已经比较稳定了。

一些开发建议

  • 使用 es6 , 让你的代码更少更可靠,有一点要注意的是 object spread 并不属于 es6 规范
// 以下的用法是不行的
this.setData{obj, ...data}

// 参数 spread 是可以的
function f(x, ...y) {
  // y is an Array
  return x * y.length;
}
  • 使用 polyfill, 微信尽管最大程序统一了 webview, 但是部分旧的 webview 依然是缺少部分常用方法的,例如:Array.find, Array.findIndex. 给出一份我使用的 polyfill.js gist.github.com/chemzqm, 代码多数来自 MDN,仅供参考
  • 使用 Promise,让你的异步并发和串行代码更容易编写和维护。因为微信旧版是没有原生 Promise 的,所以官方屏蔽了 Promise,你需要自己做一个 Promise 实现,网上有兼容标注 Promise 规范的实现,只需去掉相关 window 的判定即可:gist.github.com/chemzqm
  • 分离 UI 代码和请求逻辑,这样你的请求可以在不同页面重用,将来维护相对容易些。我们有一个 service.js 文件,里面包含了所有请求后台的函数,返回均为 Promise, 这样方便做统一的控制管理
  • 合理使用 wxml 中的 import 和 include,import 对应需要填充数据的模板,而 include 则用于引入静态的内容
  • 避免把一个页面设计的太复杂,否则一个页面对象上过多的 data 数据和方法会变得很难管理
  • 避免使用你不熟悉的第三方框架,例如使用 redux 来管理数据状态,因为小程序本身就有通过 data 属性实现了统一的数据管理,另外还提供了 devtools 工具方便调试,引入 redux 却不能在开发工具使用 redux 插件,结果可能只是增加了数据管理的难度
  • 使用官方 UI 简化设计开发流程,weui/weui-wxss: A UI library by WeChat official design team, includes the most useful widgets/modules. 如果你的项目对 UI 定制要求不是很高的话
  • 微信官方的 animation API 只是封装相关 css 属性的生成,其实质还是使用 css 的 transtion 样式,你也可以自己写相关样式后添加到元素 style 属性或者使用 animation 实现动画。出于性能考虑,不建议使用 tween 之类的库动态生成样式传递给 setData 函数
  • 使用 svg data-uri 来做 icon, 小程序的 background 里只能使用完整的 image 路径,对于针对项目的 icon 来说,使用 svg 会更为方便一些,而且svg 是矢量图,具备完美的可伸缩性,使用 utf8 格式将来也可以很容易进行调整(主要是颜色),参考 Probably Don't Base64 SVG | CSS-TricksMaterial icons - Material Design 提供了大量常用 icon 的多种格式,推荐使用。
  • 尽可能利用 flex 进行布局,因为 flex 适应性最好,而且非常灵活,
  • 合理使用 rpx 单位,rpx 是一个相当于屏幕宽度百分比的相对单位,我们在实现上对于部分padding 和 margin 样式使用了 rpx 来使得大屏上的布局获得更佳的展示效果,对于 font-size,border-width 等样式,不建议使用 rpx
  • 不考虑性能问题可以使用 css 反锯齿,让字体渲染更精细一些,只需要 app.wxss 中加入:
    page {
      text-rendering: optimizeLegibility;
      -webkit-font-smoothing: antialiased;
    }
    



遇到的一些问题及解决办法

  • html 无法使用,使用正则去掉 html 标签就是了,我们使用的代码如下:
    export function formatHTML(html) {
      html = html.replace(/<style([\s\S]*?)<\/style>/gi, '');
      html = html.replace(/<script([\s\S]*?)<\/script>/gi, '');
      html = html.replace(/<\/div>/ig, '\n');
      html = html.replace(/<\/li>/ig, '\n');
      html = html.replace(/<li>/ig, '  *  ');
      html = html.replace(/<\/ul>/ig, '\n');
      html = html.replace(/<\/p>/ig, '\n');
      html = html.replace(/<br\s*[\/]?>/gi, "\n");
      html = html.replace(/<[^>]+>/ig, '');
      html = html.replace(/\n{2,}/g, '\n\n')
      html = html.replace(/\n+$/g, '')
      html = html.replace(/&nbsp;/g, ' ')
      return html
    }
    
    性能不是很高,但是现代浏览器性能还是不错的,完全感受不到。
  • 页面深度限制,小程序出于性能角度考虑做了 5 层跳转层次的限制,解决办法就是尽可能避免深层次的交互
    • 合并多个页面,例如将搜索页面和搜索结果页面放到同一个页面上
    • 使用弹层而不是跳转,例如我们支付页面的添加收货地址使用了弹出层,而不是跳转添加页面
    • 使用 switchTab API,该接口会清理之前的页面栈
    • 使用 redirectTo 接口重定向而不是使用 navigateTo
  • 原生组件总是显示在最上层,小程序中 canvas、textarea、video 等组件使用原生渲染,如果需要弹层交互的话它们会挡住弹层。解决办法就是在弹层后将这些组件 hidden 属性设置为 true,弹出消失时重置为 false 即可。另一个问题是如果这些组件在弹层内,它们不会限制在弹层中,而是会随着页面整体进行滚动。
  • 并发请求限制,小程序限制了最多 5 个并发的 request 请求。我们遇到的一个情况是需要图片自适应高度,而小程序不支持图片高度自适应,需要显示用 wx.getImageInfo 接口获取图片尺寸信息,如果图片多于 5 个且同时请求就会触发这个限制。解决办法是使用一个 promise 队列函数,实现如下:
  • export function queue (fns, count) {
      return new Promise(function(resolve, reject) {
        let a = fns.slice(0, count)
        let b = fns.slice(count)
        let l = fns.length
        let runs = 0
        if (fns.length == 0 ) return resolve()
        for (let fn of a) {
          fn().then(() => {
            runs += 1
            if (runs == l) return resolve()
            let next = function () {
              let fn = b.shift()
              if (!fn) return
              return fn().then(() => {
                runs += 1
                if (runs == l) return resolve()
                return next()
              }, reject)
            }
            return next()
          }, reject)
        }
      })
    }
    
    第一个参数为返回 promise 的函数列表,第二个参数为并发个数,函数返回 Promise 对象。

  • 小程序 url 生成限制。如果你想设置多个参数到二维码的 url 上,以下的调用是不行的:
  •    response = RestClient.post("https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=#{token}", {
          path: "pages/affiliate/index?id=#{id}&#{code}",
          width: 430
        }.to_json)
    
    扫码后的 url 中 & 符号会变为 %2C\u0026 (感觉是微信的 bug),解决办法是使用其它自定义分割符号,例如下划线 _, 然后在小程序代码里对其进行单独解析。

最后,开发本身对于小程序而言只能算是刚开始的一小步,由于微信的流量控制,后续的铺码之路任重道远。


以上,不希望对您有所帮助,因为小程序的开发实在不是一件可以让人愉悦的事:)

编辑于 2017-03-15

文章被以下专栏收录