微信小程序开发总结:入门、原理、优化及踩坑

写于:2018/02/27
预计阅读时间:25 分钟

微信小程序发布出来已经有一年多了,记得 2017 年年初刚发布的那个时候公司业务没有涉及到小程序这块,所以只是大致浏览了下官方文档,并没有实地的去开发过小程序。最近公司业务战略上重视了小程序,所以就开始了小程序的开发计划,总的来说小程序的开发很简单,体验总体上还行!

这篇文章就当是我对小程序的个人看法和总结吧,本文仅针对小程序的“原生”开发,虽然小程序的开发很简单,但是也有一些需要注意的地方,本文结构分为以下几点:

  1. 小程序的优缺点;
  2. 小程序的开发和发布;
  3. 小程序的大致实现原理;
  4. 小程序的项目组织结构;
  5. 小程序的踩坑和吐槽;
  6. 小程序的开发优化建议。

小程序的优缺点

从开发者的角度: 小程序的优点是环境搭建成本低(开发文档清晰、发布回退非常方便)、上手无难度(相对于有些前端经验的开发者来说)和接口支持丰富(有调用底层 API 能力,功能比 H5 强大得多);缺点是布局样式难调(开发者工具的审查元素不太好用,局部细调修改很容易产生跳动)、组件化开发不够灵活(复杂组件相比于 React Vue 没那么容易掌控和梳理,书写不流畅)、灰度和测试环境很不方便(只能上传体验版测试且只能有一个体验版,并且需要管理员手动添加微信账号)和运行环境有太多限制等。

从用户的角度:优点是小程序无需到 App Store 或者应用市场下载安装,直接在微信搜索即可得到,用户无感知小程序的下载;缺点是入口有点深,打开小程序需要从发现栏或者从顶部下拉后才能看到,如果后面小程序支持固定在列表或者分享到朋友圈的话或许会有更大的使用率和传播率。

从运营的角度:优点最主要就是流量和推广了,借助微信平台的巨大流量,可以更好更容易的推广/传播自己的产品和服务,小程序推广是一个非常不错的战略方向;缺点主要是有大小限制不得超过 2M,这就使得小程序不能实现过大过复杂的应用、可以预见的是这个限制很长时间都不会解除,另外小程序毕竟不是原生 APP,交互无法做到与原生一致。

小程序的开发和发布

小程序的开发调试需要用到微信团队推出的 微信开发者工具,这个工具主要由三个部分组成:模拟器、编辑器和调试器。

模拟器基本上能达到真机的效果,同时提供了所有主流机型的模拟,另外有个特别有用的就是可以添加自定义编译场景,可以模拟从主栏进入小程序、从卡片消息进入小程序以及自定义加载页面等几十种场景,非常全面,大多数在模拟器通过的情况下在真机也是没有问题的。

调试器可以审查元素、查看 Network、控制台和查看页面数据(类似 Vuex)等,调试器其实就是一个 Chrome 内核的集成,小程序的调试其实与 Chrome 调试无异。

开发者工具自带了一个编辑器,但是我觉得不太好用,所以我都是把自带的编辑器给关掉,用自己习惯的编辑器来写代码,VSCode 也有相关的小程序插件支持高亮和提示。

小程序的大致实现原理

1. 两个 WebView 进程

小程序打包的时候会把视图相关文件(.wxml/.wxss)和业务逻辑代码(.js/.json)分开打包并启用了两个独立的 WebView 进程分别去执行,这一点与传统的 Hybird 开发有很大的不同,所以小程序限制了打开的页面层级个数,这样可以防止打开过多的 WebView 进程挤爆手机运行内存。

2. 被包装过的 RunTime

小程序编译过后的代码都可以在开发者工具里面找到,通过审查元素查看编译后的源码可以发现,业务逻辑代码在编译的时候是被整成了 AMD 模式,并且重置了一些全局变量,类似:

define('pages/index/index', function (require, module, exports, window, document) {
    // ...
})

这就是为什么在小程序中使用 window、document 等相关 API 会报错的原因。

3. 区分平台的 JavaScript 引擎

据官方称,Android 平台下小程序的 JavaScript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 53/57 内核来渲染的;iOS 平台下小程序的 JavaScript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的。具体参见:运行环境差异。当遇到两个平台表现不一致的时候也可以从 JavaScript 内核不同这个方向去排查问题所在。

4. 通过 EvaluateJavascript 更新视图

视图 WebView 和逻辑 WebView 的数据通信是依靠 WeiXinJSBridge 完成的,而且数据传输是以 String 的方式(官方称之为 EvaluateJavaScript),即:传输的数据需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。小程序中更新视图的唯一 API 是 this.setData() 据一些文章分析,小程序的 setData 操作是同步的,也就是每调用一次 setData 两个 WebView 就会进行一次通信,不会像 React 的 setState 那样异步批量更新。

小程序的项目组织结构


上图为一个小程序的项目组成图,如果你一开始看的就是官方 Demo 或其他项目代码可能会很迷惑觉得项目组织有点复杂,什么 pages、utils 和 components 文件夹的,看上去需要很多目录似的,其实小程序的基本构成只有根目录的 4 个文件:app.js(定义全局数据、初始化数据和登陆等), app.json(页面配置,文件夹配置), project.config.json(appId、场景配置等,这个文件最好不要放到 Git 仓库中,多人开发时应当列入 .gitignore)和 app.wxss(全局 CSS,可有可无)和 3 个构造器:App(), Page()Component()

另外有一点需要注意的是,App() 和 Page() 中有一些相同的生命周期函数(比如 onShow 和 onHide)会同时调用,调用顺序都是先 App 后 Page。总的来说整个小程序项目的组织结构都是可以通过 app.json 来自定义结构的,每一项的参数值参考 官方配置

小程序的踩坑和吐槽

PS:以下问题可能由于时间原因被官方所修复,如有可以留言指出。

1. 除图片外,其他所有用到的资源都有域名限制

2. 页面中文参数记得 encode/decode,除非你确定这个参数不会(被)传中文

大多数情况下页面参数是在小程序内部传递的,比如通过 wx.navigateTonavigator 组件进行带参数的页面跳转,但是有一种情况是外部定义的参数跳转,比如从其他原生应用分享小程序卡片消息过来的时候,参数中只要有中文或者特殊字符就有可能导致逻辑错乱。

3. 容器内滚动在安卓机下会产生明显的卡顿

安卓机器下,固定高度且为绝对定位的容器中的快速滚动会产生很明显的卡顿,这个与机器的性能无关,估计是小程序的一个 BUG,iOS 下不会出现;另外,这种情况是监听不到容器的滚动事件的,如果需要监听滚动事件,需要套一个 scroll-view 组件,然后通过组件事件来监听。

4. 部分操作在安卓机和 iOS 平台下的表现不一致

在小程序中打开页面 A 然后回到微信,再打开页面 B,安卓会先显示上一个页面 A,然后才跳转到页面 B,iOS 则是正常的直接去到 B 页面而看不到 A 页面。

5. rpx 在某些设备下解释成物理像素时发生异常

rpx (responsive pixel) 是小程序团队自己定义的一个布局尺寸单位,这个单位的好处是能够根据设备实际像素自动计算和适应,用这个单位来进行 CSS 布局非常省心,不需要写 Media Query,但是在某些设备下(经验证,设备像素比 pixelRatio 不为整数的设备会出现)1rpx 被解析成了 3px 物理像素,解决办法是该情况下用实际物理像素 px 来代替。

6. wx:for 列表渲染的元素会部分丢失伪类选择器的样式

比如循环渲染一个带有伪类样式(:after, :before)的 view 时,有些 view 的伪类样式会失效。

7. 修改 button 组件的部分默认样式需要改 button:after

小程序自带的 button 组件的样式很多都是通过伪类去定义的,比如你想去掉边框发现怎么重定义 border: none; 都无效,审查元素就可以发现它的边框是定义在 :after 伪类中的。

8. input 组件在获取焦点时如果页面高度变化会导致光标错位

如果你尝试在 input 组件获取焦点的时候动态改变 input 的位置,那么输入板的出现会造成光标的位置错位,微信官方建议是不做任何处理,因为无论 input 组件在页面哪个位置,获取焦点时会自动浮到输入板的上方,这已经是非常自然的交互了,没必要去强制修改它。

9. tabBar 组件是独立于页面的渲染之外的

app.json 配置文件中可以定义页面的 tabBar 选项,这个看起来像是微信主页面的那个 tabBar,在小程序的渲染过程中,tabBar 组件是独立于页面的渲染之外的,并且页面之间的切换性能是最好的。

10. CSS 不支持 important、不支持兄弟选择器 ……

11. iconfont、背景图片只能用 base64 或网络图片

在小程序是可以使用 iconfont 矢量图标的,不过只能把图标转换成 base64 格式,CSS 中的背景图片可用网络图片也可用 base64。

12. 数据绑定的页面/组件模板只支持基本的表达式

小程序的页面和组件模板(.wxml)中通过使用 {{}} 来绑定一个表达式,但是这个 Mustache 语法只能用于基本的表达式,不能有方法的调用,没有计算属性,比如这样的代码语法在小程序中是无效的:
<view>{{ foo() }}</view>
写多了 React 和 Vue 的肯定会强烈吐槽这个吧。

13. wxs 语言是一个残(biàn)疾(tài)版的 JavaScript

为了让模板中支持方法的调用,微信推出了一个新的模板语言:wxs,语法与 JavaScript 类似,但是又有很多设计得不一样的地方,比如 Object.keys() 不能用,没有 new Date(),需要调用 getDate() 代替等等,总之我觉得这个 wxs 实在是一个残疾版的 JavaScript,开发体验非常不好。

14. 微信聊天窗口中长按转发小程序不能够被检测到

使用 API 的转发(右上角三个点)是能够被检测到的,比如被转发到群,可以在小程序获取群的一些相关信息。但是如果在聊天窗口中长按转发小程序卡片给任意一个人或者群,那么这个操作就无法检测,这种场景暂时无法区分是 API 转发还是长按转发。

15. web-view 的 url 地址必须是没有经过编码的

比如 <web-view url="{{decodeURIComponent('https://qq.com')}}"/> 这样是无法加载网页的(会显示空白),如果 url 是通过参数传过来,最好确认 url 是未被编码过的。

小程序的开发优化建议

1. 静态图片、图标放到 cdn

放到本地的图片会跟代码一起打包上传,代码包体积增大,但是加载速度会很快,相当于取本地图片;放到 cdn 的图片会产生网络请求,用户网络不好的情况下可能会先看到页面,再看到图片,加载速度慢。

2. 不要把所有数据都塞到 data 中

前面讲过,由于小程序是通过 EvaluateJavascript 来更新视图的,所以如果把一些无关的数据(比如状态锁、flag 标记等)放到 data 中,也会被转成字符串来传输,这就损失了速度和性能。事实上,无论是小程序,还是 React、Vue,与视图更新无关的数据都不应该放到 state 或者 data 中,这样也有利于写出视图和逻辑清晰的代码,其他数据挂载到内部实例就好了。

3. 需要更新视图时批量整合 setData 操作

上面也提到了小程序的视图更新机制,所以尽量整合 setData 操作能提高更新性能,能合并就合并。

4. 封装经常用到的小程序 API

小程序的 API 调用大多数都是异步响应的,需要回调函数处理,如果业务中有大量的 successfail 回调会使业务逻辑看起来很乱(吃力),代码量也很多,可以考虑把他们封装成一个更容易阅读的通用方法。

5. 使用 Promise 组织异步操作

小程序中的异步操作是在太多了,如果都用回调函数按部就班去实现,那么代码写完就不认识了,好在小程序支持 ES6 支持 Promise,async/await 还在期待中。

以上就是我对小程序的理解,随着微信团队的不断完善,或许上面有些问题会在将来某一个版本修复了,说的不对或者已经更新了的麻烦留言指正。

评论列表
11
66