# 「大众点评点餐」小程序开发经验 04：逻辑层

- 作者：知晓程序
- 发布时间：2017-04-02 12:01
- 原文：https://www.ifanr.com/minapp/811428

![](https://s3.ifanr.com/wp-content/uploads/2017/03/Featured-12.jpg)

文 \| 潘逸飞

潘逸飞，美团点评工程师，2 年 Web 开发经验，现在是美团点评点餐团队的一员。

继上次谈到了[视图层开发经验](http://www.ifanr.com/minapp/803083)，本期，知晓程序（微信号 zxcx0101）想要和大家分享大众点评点餐小程序开发中，逻辑层开发的经验。

关注微信号 zxcx0101，后台回复「点评」，获取大众点评点餐小程序全套开发经验。

与视图层微信自己定义了一套与 HTML 对应的 WXML 和 WXSS 不同，小程序的逻辑层依然使用 JavaScript。但小程序的逻辑代码，与我们平常编写的 JS 还是有一些区别的。

在接下来的文章中，我会根据实际代码，进行说明。

首先，我们看看逻辑层代码结构：

```
menu
├── menu.html
├── menu.js
├── menu.json
└── menu.less
app.js
```

作为逻辑层，我们只需要关注小程序逻辑文件 `app.js` 和页面逻辑文件 `menu.js`。

### App 和 Page

#### App

小程序提供了 `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` 更具体的渲染过程可以参考下面这张图：

![](https://s3.ifanr.com/wp-content/uploads/2017/03/dzdp-1-1.jpg)

用文字简单描述这个过程，就是这样：

1. 视图层和逻辑层同时进行初始化的操作；
2. 视图层 ready 之后通知逻辑层发送数据；
3. 逻辑层执行 `onLoad` 和 `onShow` 方法，然后等待视图层的通知，在接收到视图层的通知之后发送数据给视图层，然后继续等待视图层的通知；
4. 视图层根据数据进行初次渲染后通知逻辑层渲染完毕，逻辑层调用 `onReady` 方法。然后后续的行为逻辑层可以通过再次发送数据重新渲染视图层。

`Page` 的整个工作流程可以参照下面的图：

![](https://s3.ifanr.com/wp-content/uploads/2017/03/dzdp-2-1.jpg)

首先，`Page` 的 `data` 会被用于页面的初始化渲染，之后，用户会在页面上——也就是展示层——触发事件。

比如：用户在点餐小程序，点击了加菜按钮这样的事件。页面监听到这个事件之后，会触发在 `Page` 函数中申明的自定义事件。

然后，小程序根据具体情况，可能会调用微信的 API。根据结果，我们调用 `setData` 改变页面的数据，小程序就会监听到数据的改变，然后进行重新渲染。

写过React的朋友，应该会对这个过程很熟悉。React 也是在 Component 里面申明自定义方法，触发后通过 `setState` 来重新渲染页面。

我们之前的 HTML 5 页面就是使用 React 写的，所以逻辑层迁移到小程序的代价并不是很大。

#### `getApp` 和 `getCurrentPages`

小程序内申明的变量和函数只在该文件内有效，不同的文件可以申明相同名字的变量和函数，并不会相互影响。

在上面，我们提到 `App` 内可以设置全局数据。我们在每个 `Page` 里，都可以通过全局函数 `getApp()` 来拿到全局的引用实例。之后，我们就可以利用它访问页面的数据。

比如我们在购物车下完单之后回到菜单页可能会需要进行菜单的刷新，我们在购物车页面就会调用 `getApp().data.menuRefresh = true`，然后在菜单页的 `onShow` 方法进行判断，例如:

```
let app = getApp();
Page(
    requestMenu () {   
        //刷新菜单
    };
    onShow () {
        if (app.data.menuRefresh === true) {
            app.data.menuRefresh === false;
            this.requestMenu();
        }
    }
);
```

在每个Page内，我们还可以用getCurrentPages来获取当前页面栈的实例。它返回的是数组形式的数据，第一个元素为首页，最后一个元素为当前页面。

页面栈的表现情况如下表所示：

![](https://s3.ifanr.com/wp-content/uploads/2017/03/dzdp-3-1.jpg)

需要注意的是，我们不能手动去尝试修改页面栈，我们只能根据页面栈，来分析是使用哪种微信的API来跳页面。这里的跳转API还会在下面进行讲解。

### 模块化

小程序是支持模块化的，并且支持 Common.js 的模块化写法，也就是module.exports或者exports。

小程序目前并不支持引入node\_modules，也就是并不支持第三方的模块。当我们需要使用到外部的依赖的时候，建议直接将代码拷贝到小程序的目录中，然后通过相对路径的 require 函数进行引入。

### 微信 API

小程序作为微信的一个重要功能，微信的框架提供了非常丰富的微信原生 API，可以方便的调起微信提供的能力。

除了视图层的一些原生组件外，还有一些功能性的 API，如扫码，定位，媒体播放，本地存储以及支付功能等等。

我们这次使用的较多的是，通过微信发起网络请求，以及数据存储接口。

#### 发送网络请求

微信提供了 `wx.request` 来发起请求，这个方法只能发起 HTTPS 请求。所以在开发微信小程序之前，大家得先迁一下 HTTPS。

我们自己在使用 API 的时候，还用了 `pinki`e 这个包，将 `request` 包装成了 Promise 的形式，便于开发。

需要注意的是，微信的运行环境并不是浏览器，所以没有 Cookie 的功能。我们解决用户鉴别的问题是带上用户的 token，它会在用户登录时从服务器获取，并放到 `App` 的全局数据中。

#### 数据存储

我们大众点评点餐页面上有大量的菜单数据。之前在 HTML 5 上实现时，我们会用浏览器的 `localstorage` 存起来。

这次切换到微信的 `storage`，代价很小，用了一下适配器模式，将微信的数据接口适配成我们需要的接口就好了。

这样做，也是为了以后的迭代中，让 HTML 5 与小程序使用同一套代码。

![](https://s3.ifanr.com/wp-content/uploads/2017/03/dzdp-4-1.jpg)

#### 导航

小程序为了减少用户使用的时候的困扰，小程序规定页面路径最深只能到 5 层。所以开发时，得尽量避免多层级的交互方式。

![](https://s3.ifanr.com/wp-content/uploads/2017/03/dzdp-5-1.jpg)

为了方便调用，我们从管理页面跳转的时候自己封装了一下函数。就是通过 `getCurrentPages` 来对页面栈进行分析，然后选择跳转页面的方式:

```
const app = getApp();
module.exports = function go2Page(opts) {
    if (!opts) return;
    if (!opts.url) return;
    let url = opts.url;
    //拿到当前的页面栈
    const history = getCurrentPages();
    let path = url.split('?')
    let params;
    if (path.length === 2) {
        params = path[1];
    }
    let page = path[0].split('/').pop();
    let index = -1;
    for (var i = 0; i < history.length; i++) {
        let hPath = history[i].__route__;
        let hPage = hPath.split('/').pop();
        if (page == hPage) {
            index = i;
            break;
        }
    }
    if (index === -1) {
        //如果不存在这个页面，直接跳转
        wx.navigateTo({
            url: url
        });
    } else {
        //如果存在这个页面，就回退回去
        if (params) {
            //query是处理下url参数的自己定义的函数
            params = query(params);
        }
        //将跳转的页面的参数保存到全局数据中，然后在页面中可以去拿取，store是自己申明的
        app.store(page, params);
        wx.navigateBack({
            delta: history.length - (index + 1)
        });
    }
}
```

### 小提示

由于小程序的框架并非运行在浏览器中，所以 JavaScript 在 web 端的一些能力都无法使用。除了上面提到的 Cookie，还有 DOM 元素操作也无法使用。

开发者所有代码最终会被打包成一份 JavaScript，在小程序启动的时候运行，直到小程序销毁。这一点类似于浏览器的 ServiceWorker，所以逻辑层也称之为 App Service。

原文地址：https://juejin.im/post/58b7eb2aac502e006bcbb6fd

**本文由知晓程序授权转载，关注微信号 zxcx0101，在微信后台回复「****点评****」，获取大众点评点餐小程序全套开发经验。**

![zxcx&#95;0208](https://s3.ifanr.com/wp-content/uploads/2017/02/zxcx_0208.png)
