微信小程序发布出来已经有一年多了,记得 2017 年年初刚发布的那个时候公司业务没有涉及到小程序这块,所以只是大致浏览了下官方文档,并没有实地的去开发过小程序。最近公司业务战略上重视了小程序,所以就开始了小程序的开发计划,总的来说小程序的开发很简单,体验总体上还行!
这篇文章就当是我对小程序的个人看法和总结吧,本文仅针对小程序的“原生”开发,虽然小程序的开发很简单,但是也有一些需要注意的地方,本文结构分为以下几点:
从开发者的角度: 小程序的优点是环境搭建成本低(开发文档清晰、发布回退非常方便)、上手无难度(相对于有些前端经验的开发者来说)和接口支持丰富(有调用底层 API 能力,功能比 H5 强大得多);缺点是布局样式难调(开发者工具的审查元素不太好用,局部细调修改很容易产生跳动)、组件化开发不够灵活(复杂组件相比于 React Vue 没那么容易掌控和梳理,书写不流畅)、灰度和测试环境很不方便(只能上传体验版测试且只能有一个体验版,并且需要管理员手动添加微信账号)和运行环境有太多限制等。
从用户的角度:优点是小程序无需到 App Store 或者应用市场下载安装,直接在微信搜索即可得到,用户无感知小程序的下载;缺点是入口有点深,打开小程序需要从发现栏或者从顶部下拉后才能看到,如果后面小程序支持固定在列表或者分享到朋友圈的话或许会有更大的使用率和传播率。
从运营的角度:优点最主要就是流量和推广了,借助微信平台的巨大流量,可以更好更容易的推广/传播自己的产品和服务,小程序推广是一个非常不错的战略方向;缺点主要是有大小限制不得超过 2M,这就使得小程序不能实现过大过复杂的应用、可以预见的是这个限制很长时间都不会解除,另外小程序毕竟不是原生 APP,交互无法做到与原生一致。
小程序的开发调试需要用到微信团队推出的 微信开发者工具,这个工具主要由三个部分组成:模拟器、编辑器和调试器。
模拟器基本上能达到真机的效果,同时提供了所有主流机型的模拟,另外有个特别有用的就是可以添加自定义编译场景,可以模拟从主栏进入小程序、从卡片消息进入小程序以及自定义加载页面等几十种场景,非常全面,大多数在模拟器通过的情况下在真机也是没有问题的。
调试器可以审查元素、查看 Network、控制台和查看页面数据(类似 Vuex)等,调试器其实就是一个 Chrome 内核的集成,小程序的调试其实与 Chrome 调试无异。
开发者工具自带了一个编辑器,但是我觉得不太好用,所以我都是把自带的编辑器给关掉,用自己习惯的编辑器来写代码,VSCode 也有相关的小程序插件支持高亮和提示。
小程序打包的时候会把视图相关文件(.wxml/.wxss)和业务逻辑代码(.js/.json)分开打包并启用了两个独立的 WebView 进程分别去执行,这一点与传统的 Hybird 开发有很大的不同,所以小程序限制了打开的页面层级个数,这样可以防止打开过多的 WebView 进程挤爆手机运行内存。
小程序编译过后的代码都可以在开发者工具里面找到,通过审查元素查看编译后的源码可以发现,业务逻辑代码在编译的时候是被整成了 AMD 模式,并且重置了一些全局变量,类似:
define('pages/index/index', function (require, module, exports, window, document) {
// ...
})
这就是为什么在小程序中使用 window、document 等相关 API 会报错的原因。
据官方称,Android 平台下小程序的 JavaScript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 53/57 内核来渲染的;iOS 平台下小程序的 JavaScript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的。具体参见:运行环境差异。当遇到两个平台表现不一致的时候也可以从 JavaScript 内核不同这个方向去排查问题所在。
视图 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:以下问题可能由于时间原因被官方所修复,如有可以留言指出。
大多数情况下页面参数是在小程序内部传递的,比如通过
wx.navigateTo
或navigator
组件进行带参数的页面跳转,但是有一种情况是外部定义的参数跳转,比如从其他原生应用分享小程序卡片消息过来的时候,参数中只要有中文或者特殊字符就有可能导致逻辑错乱。
安卓机器下,固定高度且为绝对定位的容器中的快速滚动会产生很明显的卡顿,这个与机器的性能无关,估计是小程序的一个 BUG,iOS 下不会出现;另外,这种情况是监听不到容器的滚动事件的,如果需要监听滚动事件,需要套一个
scroll-view
组件,然后通过组件事件来监听。
在小程序中打开页面 A 然后回到微信,再打开页面 B,安卓会先显示上一个页面 A,然后才跳转到页面 B,iOS 则是正常的直接去到 B 页面而看不到 A 页面。
rpx
(responsive pixel) 是小程序团队自己定义的一个布局尺寸单位,这个单位的好处是能够根据设备实际像素自动计算和适应,用这个单位来进行 CSS 布局非常省心,不需要写 Media Query,但是在某些设备下(经验证,设备像素比pixelRatio
不为整数的设备会出现)1rpx 被解析成了 3px 物理像素,解决办法是该情况下用实际物理像素px
来代替。
比如循环渲染一个带有伪类样式(:after, :before)的
view
时,有些view
的伪类样式会失效。
小程序自带的 button 组件的样式很多都是通过伪类去定义的,比如你想去掉边框发现怎么重定义
border: none;
都无效,审查元素就可以发现它的边框是定义在:after
伪类中的。
如果你尝试在 input 组件获取焦点的时候动态改变 input 的位置,那么输入板的出现会造成光标的位置错位,微信官方建议是不做任何处理,因为无论 input 组件在页面哪个位置,获取焦点时会自动浮到输入板的上方,这已经是非常自然的交互了,没必要去强制修改它。
在
app.json
配置文件中可以定义页面的 tabBar 选项,这个看起来像是微信主页面的那个 tabBar,在小程序的渲染过程中,tabBar 组件是独立于页面的渲染之外的,并且页面之间的切换性能是最好的。
在小程序是可以使用 iconfont 矢量图标的,不过只能把图标转换成 base64 格式,CSS 中的背景图片可用网络图片也可用 base64。
小程序的页面和组件模板(.wxml)中通过使用
{{}}
来绑定一个表达式,但是这个 Mustache 语法只能用于基本的表达式,不能有方法的调用,没有计算属性,比如这样的代码语法在小程序中是无效的:
<view>{{ foo() }}</view>
写多了 React 和 Vue 的肯定会强烈吐槽这个吧。
为了让模板中支持方法的调用,微信推出了一个新的模板语言:
wxs
,语法与 JavaScript 类似,但是又有很多设计得不一样的地方,比如Object.keys()
不能用,没有new Date()
,需要调用getDate()
代替等等,总之我觉得这个wxs
实在是一个残疾版的 JavaScript,开发体验非常不好。
使用 API 的转发(右上角三个点)是能够被检测到的,比如被转发到群,可以在小程序获取群的一些相关信息。但是如果在聊天窗口中长按转发小程序卡片给任意一个人或者群,那么这个操作就无法检测,这种场景暂时无法区分是 API 转发还是长按转发。
比如
<web-view url="{{decodeURIComponent('https://qq.com')}}"/>
这样是无法加载网页的(会显示空白),如果 url 是通过参数传过来,最好确认 url 是未被编码过的。
放到本地的图片会跟代码一起打包上传,代码包体积增大,但是加载速度会很快,相当于取本地图片;放到 cdn 的图片会产生网络请求,用户网络不好的情况下可能会先看到页面,再看到图片,加载速度慢。
前面讲过,由于小程序是通过 EvaluateJavascript 来更新视图的,所以如果把一些无关的数据(比如状态锁、flag 标记等)放到 data 中,也会被转成字符串来传输,这就损失了速度和性能。事实上,无论是小程序,还是 React、Vue,与视图更新无关的数据都不应该放到
state
或者data
中,这样也有利于写出视图和逻辑清晰的代码,其他数据挂载到内部实例就好了。
上面也提到了小程序的视图更新机制,所以尽量整合 setData 操作能提高更新性能,能合并就合并。
小程序的 API 调用大多数都是异步响应的,需要回调函数处理,如果业务中有大量的
success
和fail
回调会使业务逻辑看起来很乱(吃力),代码量也很多,可以考虑把他们封装成一个更容易阅读的通用方法。
小程序中的异步操作是在太多了,如果都用回调函数按部就班去实现,那么代码写完就不认识了,好在小程序支持 ES6 支持 Promise,async/await 还在期待中。
以上就是我对小程序的理解,随着微信团队的不断完善,或许上面有些问题会在将来某一个版本修复了,说的不对或者已经更新了的麻烦留言指正。