# 逻辑层（App Service）

> 官方文档：https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/

逻辑层使用 JavaScript 引擎运行，不在浏览器中，没有 `window`、`document` 等 Web API。

## App() — 注册小程序

```javascript
App({
  onLaunch(options) {
    // 小程序初始化时触发，全局只触发一次
    // options.scene — 场景值
    // options.query — 启动参数
    // options.path — 启动页面路径
  },
  onShow(options) {
    // 小程序启动或从后台进入前台时触发
  },
  onHide() {
    // 小程序从前台进入后台时触发
  },
  onError(msg) {
    // 小程序发生脚本错误或 API 调用失败时触发
    console.error(msg)
  },
  onUnhandledRejection(res) {
    // 未处理的 Promise 拒绝事件
    console.warn(res.reason, res.promise)
  },
  onPageNotFound(res) {
    // 页面不存在时触发
    wx.redirectTo({ url: 'pages/...' })
  },
  onThemeChange({ theme }) {
    // 系统主题变更（dark / light）
  },
  globalData: {
    userInfo: null
  }
})
```

获取 App 实例：
```javascript
const app = getApp()
console.log(app.globalData)
```

**注意**：
- 不要在 `App()` 内调用 `getApp()`，使用 `this` 即可
- 不要在 `onLaunch` 时调用 `getCurrentPages()`，此时 page 还没有生成

## Page() — 注册页面

```javascript
Page({
  data: {
    text: 'Hello',
    array: [{ msg: '1' }, { msg: '2' }]
  },

  // ===== 生命周期 =====
  onLoad(options) {
    // 页面加载时触发，options 为页面路由参数
    // 一个页面只会调用一次
  },
  onShow() {
    // 页面显示/切入前台时触发
  },
  onReady() {
    // 页面初次渲染完成时触发
    // 一个页面只会调用一次，代表页面已可以和视图层交互
  },
  onHide() {
    // 页面隐藏/切入后台时触发（如 navigateTo 或底部 tab 切换）
  },
  onUnload() {
    // 页面卸载时触发（如 redirectTo 或 navigateBack）
  },

  // ===== 页面事件处理 =====
  onPullDownRefresh() {
    // 下拉刷新（需在 json 中开启 enablePullDownRefresh）
    // 处理完后调用 wx.stopPullDownRefresh()
  },
  onReachBottom() {
    // 上拉触底（可在 json 中设置 onReachBottomDistance）
  },
  onShareAppMessage(res) {
    // 用户点击右上角转发
    // res.from: 'button' 或 'menu'
    return {
      title: '自定义转发标题',
      path: '/pages/index/index',
      imageUrl: '' // 自定义图片路径
    }
  },
  onShareTimeline() {
    // 分享到朋友圈（基础库 2.11.3+）
    return { title: '', query: '', imageUrl: '' }
  },
  onPageScroll(res) {
    // 页面滚动时触发，res.scrollTop 为垂直滚动距离(px)
    // 注意：频繁触发，避免在此做复杂操作
  },
  onResize(res) {
    // 页面尺寸变化时触发（如屏幕旋转）
  },
  onTabItemTap(item) {
    // 当前是 tab 页时，点击 tab 时触发
    // item.index / item.pagePath / item.text
  },

  // ===== 自定义方法 =====
  viewTap() {
    this.setData({
      text: 'Set some data for updating view.'
    })
  }
})
```

### 页面生命周期顺序

```
onLoad → onShow → onReady → [onHide → onShow] → onUnload
```

### setData 详解

```javascript
// 基本用法
this.setData({
  text: 'changed data'
})

// 修改数组某一项
this.setData({
  'array[0].msg': 'changed'
})

// 修改对象某个属性
this.setData({
  'object.key': 'value'
})

// 带回调
this.setData({ text: 'new' }, function() {
  // setData 引起的界面更新渲染完毕后的回调
})
```

**setData 性能注意事项**：
- 数据量不宜过大（单次 setData 不超过 1MB，建议不超过 256KB）
- 不要频繁调用（如 onPageScroll 中不要每次都 setData）
- 只传需要更新的数据，不要整个 data 都传
- 后台页面不要 setData（页面 onHide 后避免 setData）

## 页面路由

框架以栈的形式维护当前所有页面，最多 10 层。

| 路由方式 | 触发时机 | 路由前页面 | 路由后页面 |
|----------|----------|-----------|-----------|
| 初始化 | 小程序打开第一个页面 | | onLoad, onShow |
| 打开新页面 | wx.navigateTo / `<navigator open-type="navigate">` | onHide | onLoad, onShow |
| 页面重定向 | wx.redirectTo / `<navigator open-type="redirect">` | onUnload | onLoad, onShow |
| 页面返回 | wx.navigateBack / 用户左上角返回 | onUnload | onShow |
| Tab 切换 | wx.switchTab / `<navigator open-type="switchTab">` / 用户切换 Tab | | 各种情况 |
| 重启动 | wx.reLaunch / `<navigator open-type="reLaunch">` | onUnload | onLoad, onShow |

```javascript
// 保留当前页面，跳转到新页面（页面栈 +1）
wx.navigateTo({ url: '/pages/detail/detail?id=1' })

// 关闭当前页面，跳转到新页面（页面栈不变）
wx.redirectTo({ url: '/pages/detail/detail?id=1' })

// 关闭所有页面，打开某个页面
wx.reLaunch({ url: '/pages/index/index' })

// 跳转到 tabBar 页面，关闭其他所有非 tabBar 页面
wx.switchTab({ url: '/pages/index/index' })

// 返回上一页（delta 为返回的页面数）
wx.navigateBack({ delta: 1 })
```

### 页面间通信（EventChannel）

```javascript
// 页面 A
wx.navigateTo({
  url: '/pages/B/B',
  events: {
    // 监听来自 B 页面的事件
    acceptDataFromOpenedPage(data) {
      console.log(data)
    }
  },
  success(res) {
    // 向 B 页面发送数据
    res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' })
  }
})

// 页面 B
Page({
  onLoad() {
    const eventChannel = this.getOpenerEventChannel()
    // 向 A 页面发送数据
    eventChannel.emit('acceptDataFromOpenedPage', { data: 'from B' })
    // 监听来自 A 页面的数据
    eventChannel.on('acceptDataFromOpenerPage', (data) => {
      console.log(data)
    })
  }
})
```

## 模块化

```javascript
// common.js
function sayHello(name) {
  console.log(`Hello ${name}!`)
}
module.exports.sayHello = sayHello

// 使用
const common = require('common.js')
common.sayHello('MINA')
```

**注意**：
- 小程序不支持直接引入 `node_modules`，需使用 npm 构建或手动拷贝
- 每个文件有独立作用域，不同文件中可声明同名变量
- 通过 `getApp()` 获取全局数据

## getCurrentPages()

```javascript
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] // 当前页面
const prevPage = pages[pages.length - 2]    // 上一个页面
// 可以通过 prevPage.setData() 修改上一页数据（返回时生效）
```

## 在线查询

如需更详细信息，可抓取：
- App 参考：https://developers.weixin.qq.com/miniprogram/dev/reference/api/App.html
- Page 参考：https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html
- 路由：https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html
