文 | 潘逸飞潘逸飞,美团点评工程师,2 年 Web 开发经验,现在是美团点评点餐团队的一员。
继上次谈到了视图层开发经验,本期,知晓程序(微信号 zxcx0101)想要和大家分享大众点评点餐小程序开发中,逻辑层开发的经验。
关注微信号 zxcx0101,后台回复「点评」,获取大众点评点餐小程序全套开发经验。
与视图层微信自己定义了一套与 HTML 对应的 WXML 和 WXSS 不同,小程序的逻辑层依然使用 JavaScript。但小程序的逻辑代码,与我们平常编写的 JS 还是有一些区别的。在接下来的文章中,我会根据实际代码,进行说明。首先,我们看看逻辑层代码结构:
作为逻辑层,我们只需要关注小程序逻辑文件 app.js
和页面逻辑文件 menu.js
。App 和 PageApp小程序提供了 App
方法来注册整个小程序。在 App
方法里,我们可以传入一个对象,指定小程序的生命周期函数以及自定义的函数或者数据。需要注意的是,这个函数只能被调用一次。在 App
方法中,我们可以使用这些生命周期及功能性参数:(重庆微信小程序)
globalData
onLaunch
onShow
onHide
onError
其他自定义的函数
如上所示,App
拥有着 4 个生命周期函数。通过这些函数,我们可以在小程序状态更变时,进行一些全局信息的获取。例如启动小程序时,获取用户信息、门店信息等等,然后存入到全局数据中。这里的数据,可以被每个页面访问。Page小程序针对每个页面提供了 Page
的函数。整个逻辑层大部分的代码都会写在 Page
函数中,Page
中承接着整个页面的数据、生命周期函数,以及在视图中绑定的事件的触发函数(例如点击事件)。整个 Page
函数允许的参数如下所示:
data
onload
onReady
onShow
onHide
onUnload
onPullDownRefresh
onReachBottom
onShareAppMessage
其他自定义函数
如上,Page
函数因为是页面级别的,所以拥有着更多的生命函数,会有下拉刷新事件、页面到达底部的事件。在这里,我们需要区别好各个生命周期函数。例如:
onLoad
只会在初始化的时候调用一次。onShow
是每次打开页面都会调用。onReady
只有页面初次渲染完成才会被调用。onHide
会在页面跳转或底部 Tab 切换时调用。onUnload
会在页面从页面堆栈中销毁前调用。
Page
更具体的渲染过程可以参考下面这张图:用文字简单描述这个过程,就是这样:
视图层和逻辑层同时进行初始化的操作;
视图层 ready 之后,通知逻辑层发送数据;
逻辑层执行
onLoad
和onShow
方法,然后等待视图层的通知,在接收到视图层的通知之后发送数据给视图层,然后继续等待视图层的通知;视图层根据数据进行初次渲染后通知逻辑层渲染完毕,逻辑层调用
onReady
方法。然后后续的行为逻辑层可以通过再次发送数据重新渲染视图层。
Page
的整个工作流程可以参照下面的图:首先,
Page
的 data
会被用于页面的初始化渲染,之后,用户会在页面上——也就是展示层——触发事件。举个例子:用户在点餐小程序,产生了「点击加菜按钮」这样的事件。页面监听到这个事件之后,会触发在 Page
函数中申明的自定义事件。然后,小程序根据具体情况,可能会调用微信的 API。根据结果,我们调用 setData
改变页面的数据,小程序就会监听到数据的改变,然后进行重新渲染。写过 React 的朋友,应该会对这个过程很熟悉。React 也是在 Component 里面申明自定义方法,触发后通过 setState
来重新渲染页面。我们之前的 HTML 5 页面就是使用 React 写的,所以逻辑层迁移到小程序的代价并不是很大。getApp
和 getCurrentPages
小程序内申明的变量和函数只在该文件内有效,不同的文件可以申明相同名字的变量和函数,并不会相互影响。在上面,我们提到 App
内可以设置全局数据。我们在每个 Page
里,都可以通过全局函数 getApp(
)
来拿到全局的引用实例。之后,我们就可以利用它访问页面的数据。比如我们在购物车下完单之后回到菜单页可能会需要进行菜单的刷新,我们在购物车页面就会调用 getApp
().data.menuRefresh = true
,然后在菜单页的 onShow
方法进行判断,例如:
在每个 Page 内,我们还可以用 getCurrentPages
来获取当前页面栈的实例。它返回的是数组形式的数据,第一个元素为首页,最后一个元素为当前页面。页面栈的表现情况如下表所示:需要注意的是,我们不能手动去尝试修改页面栈,我们只能根据页面栈,来分析是使用哪种微信的 API 来跳页面。这里的跳转 API 还会在下面进行讲解。模块化小程序是支持模块化的,并且支持 Common.js 的模块化写法,也就是
module.exports
或者 exports
。小程序目前并不支持引入 node_modules
,也就是并不支持第三方的模块。当我们需要使用到外部的依赖的时候,建议直接将代码拷贝到小程序的目录中,然后通过相对路径的 require
函数进行引入。微信 API小程序作为微信的一个重要功能,微信的框架提供了非常丰富的微信原生 API,可以方便的调起微信提供的能力。除了视图层的一些原生组件外,还有一些功能性的 API,如扫码,定位,媒体播放,本地存储以及支付功能等等。我们这次使用的较多的是,通过微信发起网络请求,以及数据存储接口。发送网络请求微信提供了 wx.request
来发起请求,这个方法只能发起 HTTPS 请求。所以在开发微信小程序之前,大家得先迁一下 HTTPS。我们自己在使用 API 的时候,还用了 pinkie
这个包,将 request
包装成了 Promise 的形式,便于开发。需要注意的是,微信的运行环境并不是浏览器,所以没有 Cookie 的功能。我们解决用户鉴别的问题是带上用户的 token,它会在用户登录时从服务器获取,并放到 App
的全局数据中。数据存储我们大众点评点餐页面上有大量的菜单数据。之前在 HTML 5 上实现时,我们会用浏览器的 localstorage
存起来。这次切换到微信的 storage
,代价很小,用了一下适配器模式,将微信的数据接口适配成我们需要的接口就好了。这样做,也是为了以后的迭代中,让 HTML 5 与小程序使用同一套代码。导航小程序为了减少用户使用的时候的困扰,小程序规定页面路径最深只能到 5 层。所以开发时,得尽量避免多层级的交互方式。
为了方便调用,我们从管理页面跳转的时候自己封装了一下函数。就是通过
getCurrentPages
来对页面栈进行分析,然后选择跳转页面的方式:
小提示由于小程序的框架并非运行在浏览器中,所以 JavaScript 在 web 端的一些能力都无法使用。除了上面提到的 Cookie,还有 DOM 元素操作也无法使用。开发者所有代码最终会被打包成一份 JavaScript,在小程序启动的时候运行,直到小程序销毁。这一点类似于浏览器的 ServiceWorker,所以逻辑层也称之为 App Service。