从小程序诞生伊始,就有很多人开始研习小程序的机理和特点,从源代码的角度、从整体架构的角度,有很多不错的文章会令人受益。
但理论是一回事,真正理解小程序,还是需要一定的实践,才能进一步去理解小程序背后的一些想法,它和现有平台的一些异同,以及如何去适应它,做出更有趣的小程序。
小程序的编程模式
最近,我们在做「轻芒小程序+」和其它轻芒产品的小程序应用过程中,对小程序有了进一步的理解,进而有了本文。
去理解一个开发平台的特性,一个不错的角度就是从"编程模式"入手,就是看在这个平台上去开发,需要如何书写和组织自己的代码,进而搞清楚三个问题:
- 数据如何获取
- 界面如何呈现
- 交互如何传导
换而言之,就是从 MVC(Model-View-Controller)的视角去拆解这个平台的特性,从而理解在这个平台上开发有何特点。
数据如何获取?
程序的本质,可以说就是数据的呈现和加工。所以,看一个客户端开发平台的基本能力,首先,就要看能把哪些数据放在上面处理,有哪些局限性,如果缺少了必要的数据获取方式,那对于开发者而言,巧妇也难为无米之炊。
从这点看,小程序是提供的数据获取方式算是非常丰富了,大概涵盖:
通过 Https 请求去服务端获取数据。支持 Http 是最基本的,小程序对 Http 有限制,除了要求通信协议是 Https,出现的域名必须提前预设之外,还将应用层协议限定到了 Json 格式下,这一点,可能比任何一个已有客户端平台更为严苛。站在小程序的平台角度来看,通过这样的协议规定,对应用中流动的数据有了更强的管控能力;而对于开发者而言,则需要花些时间去调整自己的服务协议以便适应小程序的要求。
可以在本地文件系统上存取数据。小程序提供了算是丰富的 APIs 供开发者在手机系统上存取文件。开发者可以本地文件来做缓存、做状态记忆,等等,为开发提供了不错的便利。
可以读写设备中的一部分信息。小程序开放了一些 APIs,帮助开发者获得设备上的一些基本信息,比如:手机型号、屏幕尺寸,网络状态,等等。比较有价值的,是可以选择获取手机上的图片等多媒体文件,这给做一些图像相关的应用提供了可能性;以及它还提供了不少设备上罗盘、重力感应器、地理位置等相关的信息,对开发者理解用户所处的环境有很大帮助。
从上面的介绍不难看出,小程序中的数据获取方式,和一般的浏览器提供的相仿(也就是和做 Html5 应用能获取的信息),比原生的客户端更局限一些,但对于绝大多数的应用而言,是足够用了。
除此之外, 小程序提供了微信生态中的一些数据,比如账号信息。这对于微信庞大的生态而言,只是非常小的一部分数据,但确是开发小程序应用中最值得利用的一部分数据。
举个例子,在其它平台上,如果需要获取到微信的账号信息,需要通过一次用户授权。如果用户暂时不想提供,则会使得程序出于 “未登录” 状态,给整个服务的展开带来困难。而在小程序中,只要用户点开小程序,就意味着完成了授权,开发者可以直接读取到小程序的账号信息,并可以同步到自己的服务端作为该用户的身份标识,从而实现 “始终登录” 的状态,使得后续服务可以更好的提供。
一份可行的示例如下:
- // 先调用登录接口,获得请求码
- wx.login({
- success: function (res) {
- // 获取到请求码,继续请求用户的基本信息
- var code = res.code
- wx.getUserInfo({
- success: function (res) {
- // 获取到了加密的用户信息,去服务端解密并存储
- var userData = res.encryptedData
- var iv = res.iv
- wx.request({
- url: 'https://my_account/...',
- data: {
- code: code,
- user_data: userData,
- iv: iv
- },
- success: function(res) {
- // 在服务器上,解析并生成自己的账号验证信息
- var user = res.data.user
- var token = res.data.token
- // 并且还可以存在本地存储上,供下次打开使用
- wx.setStorage({
- key: 'my_token',
- data: token
- })
- }
- })
- }
- })
- }
- });
界面如何呈现?
小程序刚发布的时候,一片人开始惊呼 Html5 的时代就要到来了,因为小程序在界面层,使用了 **Html/CSS/Javascript** 这套 Html5 的技术栈。但很快,随着聪明的程序员们对小程序的理解进一步加深,就发现小程序所说的 Html/CSS/Javascript 和 Html5 中的完全不是一回事,其差异,基本等同于 Java 和 Javscript 间的差距。
在小程序中,和 Html 对应的是 **WXML** ,它保留下来的只有 Html 的概念,而传统的 `<div>` `<a>` 标签都完全被抛弃了。和 Facebook 的 React 类似,小程序引入了自己的 Html 标签,它和 `<article>` `<section>` 这样的语义标签不同,小程序中的标签,更像是传统客户端开发中的 **组件** (或者叫控件),每个组件都有自己背后的职能和使用方式。比如:如果需要展示图片,就只能用 `<image>` 标签,其它标签都无法承载,而如果需要提供可选的文本,则只能使用 `<text>` 标签,等等。这样的方式带来最大的问题是传统的 Html 页面都无法在小程序中呈现(而小程序正好,没提供类似 Web View 的客户端控件)。
比如,大量的内容网站,其文章内容都是存储为一个 Html 片段的,这样就无法直接呈现在小程序中。如果需要展示,一个思路是构建一个中间服务,将 Html 转译成一种更简单利于渲染的中间格式数据,然后,在小程序端把中间格式的数据转换成小程序的标签进行呈现。我们在做 [轻芒生活](http://s.qingmang.mobi/10c) 的时候,正好设计并实现了一个转义服务,将任意一个 Html 页面转换成中间格式(内部名是 RAML),解决了内容性 Html 页在小程序上的呈现问题。
(在小程序中呈现 Html 内容页)
和 Html 相比,小程序的 **WXSS** 算是比较完整的保留了 CSS 的特征,这一点还蛮出乎意料的。WXSS 在语义上最大的不同,一是在于它支持了相对尺寸单位 `rpx` ,每 750rpx 等价于当前设备的屏幕宽度。这个相对尺寸单位的引入,把那种繁复的屏幕尺寸适配变得简单了不少。而和 CSS 的另一个不同,是它更像传统控件样式用法,不支持 CSS3 那么多的选择器,使用中,更多的是一个控件一个 class 这样来使用。
小程序中虽然支持 ES6 标准的 Javascript,但窗口级的 Javascript 在小程序中完全被废弃掉了,开发者无法用 Javascript 去调用 window、document 对象来修改界面元素完成逻辑。小程序中的 Javascript 其实直接对应 node.js 的用法,用来完成后台业务逻辑,而不是直接控制交互。小程序的这个设计,使其可以用到 virtual dom 的方式来渲染界面,让界面数据更新时的性能优化成为可能,但付出的代价就是少了窗口级 Javscript 的那层胶水的黏合,使得很多功能的开发变得极其呆板和繁复。
交互如何传导?
所谓交互的传导,是当用户和界面发生交互式,平台框架通过何种方式告诉业务层,并将处理后的变化呈现回交互界面上。如果把 WXSS + WXML 绘制的页面看成 “前端”,把 Javascript 撰写的业务逻辑看成 “后端”,你会发现,小程序的前后端交互特别像 Web 1.0 的模式,前端把交互行为封装成 **事件(event)** 发送到后端,后端处理完成后,通过 **setData** 方法将数据回传到前端。
(小程序的交互传导)
小程序提供的 Events,基础的有类似单击、长按、触摸、滑动,等等,对于视频播放器等控件,还有监听播放、暂停等等。这些事件涵盖算是比较基础的,没有更高级的手势、多点触控等相关事件,但也还是足够让开发者具体了解用户的输入,进而做出响应。
而小程序给界面相应的唯一方式,是通过 Page 中的 setData API 对界面上的数据进行更新, **小程序会比较两次调用期间数据的变化** ,来决策需要更新哪部分的交互界面。
举个实际的例子,假设开发者需要做一个滑动切换页面的效果,在小程序中该如何实现?首先,是将变量数据引入渲染页面:
<view class="page" id="current-page" style="left:{{distance}}rpx;" bindtouchstart="movePage" bindtouchcancel="movePage" bindtouchmove="movePage" bindtouchend="movePage"> </view>
可以看到, `distance` 是一个模版参数,它初始值为 0,表示移动的距离。通过 **bindtouchstart** 等函数绑定上 Javascript 的方法,将事件回传。
- movePage: function(event) {
- var status = {
- needUpdate: false,
- distance: 0
- }
- // 处理各种事件,计算是否需要刷新,和移动方向
- if ("touchstart" === event.type) {
- // 开始计算移动
- ...
- } else if ("touchend" === event.type) {
- // 判定移动的距离是否足够.
- ...
- } else if ("touchcancel" === event.type) {
- // 被打断就算了.
- ...
- } else if ("touchmove" === event.type) {
- // 计算移动距离
- ...
- }
- // 根据移动的距离,来更新界面
- if (status.needUpdate) {
- this.setData({
- distance: status.distance
- })
- }
- }
而在 Javascript 一端,则捕获事件、计算偏移量,然后将新的偏移量送到前端界面。
从这里可以看到,小程序的交互模式,是典型的单向模式,前端回传事件,数据单向的推到前端,而不是通过类似 “变量” “状态” 这样的方式来告知。这样的模式下,开发者对界面变化的控制往往不可能太精准,整个核心,都依赖小程序对两次数据变化的 diff 计算,这个会最终影响整个交互的性能。
小程序开发模式的特点
至此,我们可以来总结一下小程序开发的一些特点了。整体来看,小程序是借了 Html5 的技术栈,行了传统客户端开发的模式,这一点和 React 等平台会比较相近,可以视为 HTMLl5 的一个新分支。
从设计思路看,小程序做了大量的 “限制”,最大的限制,是开发者其实是无法通过 Javascript 这样的编程语言,直接对界面进行控制,而是通过数据驱动来间接实现。这对于缺少开发经验的人而言,是有益的事情,因为这降低了理解的门槛,但对于复杂的应用而言,这个模式开发起来比较呆板,往往是一个变化多处修改,增加了理解代码的成本。
开发小程序的坑
开发小程序的日子,也是一个踩坑的历程。简单总结,小程序中的坑大概来自这几个方面:
和 Web 的兼容性。小程序引入了 Html/CSS 做为技术栈,并在其基础上进行了定制。很多开发中的问题都来自于 “定制”,因为你并不知道哪部分就被定制了,哪部分是被继承了。比如,你用了一个 CSS 语法,发现并不生效,或者效果和浏览器中的不一样,于是,你只能换一个写法,结果很有可能,又会继续发现,这个新的写法可能效果也不对,于是你只能继续尝试,如此反复,可能会消耗大量的时间。
开发环境不稳定。小程序的开发,是基于微信自制的一个 IDE,但当下,IDE 的稳定性、易用性都非常之差,时常出现 Bug,你以为是你的程序写错了,但其实,是 IDE 的 Bug,重启一下 IDE,一切都引刃而解了。于是,当你日后开发小程序时出现某种异样,先重启 IDE,再看问题还在不在,也许是种更节省时间的方式。
缺少真机调试环境。小程序的运行时其实就是微信,微信几乎没提供任何真机上调试工具给你(也不能说完全没有,有一个只能在真机上瞪着眼睛看的日志框)。你在模拟器中调试好的程序,可能在真机上运行起来并不如预期。比如,我们碰到过真机上白屏、位置错乱、动画效果不对,以及 Android 上至今还不能运行,等等问题。这对于稍微复杂的程序而言,颇为梦魇,想做一些细粒度的调整和优化,基本只能靠猜。
闭源且缺少学习资料。小程序整体上是闭源状态(虽然模拟器和 IDE 部分可以通过反编译来看),且缺少足够的学习资料,如果一旦碰到控件如何使用、为什么这么用不对,之类的问题,就只能靠不停的试来解决,也需要耗费大量时间。
最后介绍下「轻芒小程序+」。「轻芒小程序+」是由轻芒团队提出的小程序解决方案,它将替内容创业者免费搭建属于自己的微信小程序,其创建的小程序在内容发布之外,还将具有评论、笔记、付费阅读等特色功能。轻芒小程序+ 不打算创建一个新的内容平台,而是将内容创业者现有的自媒体账号转化成微信小程序。内容创业者只要照常更新自媒体,这些内容就能自动更新到小程序。
简而言之,做为一个新的开发平台,微信小程序从本身的稳定性,以及配套的工具链上都不算完善,这对于早期开发者而言,需要耗费额外精力去尝试和探索,但这也许就是一个新平台的价值和代价吧。