企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## 2.2 程序与页面 从逻辑组成来说,一个小程序是由多个`页面`组成的`程序`。 而通常`程序`在启动或者退出的时候存储数据或者在“页面”显示或者隐藏的时候做一些逻辑处理, ### 2.2.1 程序 宿主环境(客户端)提供了`App()` 构造器用来注册一个程序App >[warning] `App()` 构造器必须写在项目根目录的app.js文件里 App实例是单例对象,在其他JS脚本中可以使用宿主环境提供的 `getApp()` 来获取程序实例。 代码清单3-3 getApp() 获取App实例 ~~~ // other.js var appInstance = getApp() ~~~ #### 2.2.1.1 程序构造器--`App()` App() 的调用方式如代码清单3-4所示,App构造器接受一个Object参数,参数说明如表3-1所示,其中onLaunch / onShow / onHide 三个回调是App实例的生命周期函数,我们会在后文展开;onError我们暂时不在本章展开,我们会在第8章里详细讨论;App的其他参数我们也放在后文进行展开。 代码清单3-4 App构造器 ~~~ App({ onLaunch: function(options) {}, onShow: function(options) {}, onHide: function() {}, onError: function(msg) {}, globalData: 'I am global data' }) ~~~ :-: 表3-1 App构造器的参数 | 属性 | 类型 | 描述 | 触发时机 | | --- | --- | --- | --- | | onLaunch | Function | 生命周期函数--监听小程序初始化 | 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) | | onShow | Function | 生命周期函数--监听小程序显示 | 当小程序启动,或从后台进入前台显示,会触发 onShow| | onHide | Function | 生命周期函数--监听小程序隐藏 | 当小程序从前台进入后台,会触发 onHide | | onError | Function | 错误监听函数 | 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息 | | onPageNotFound | Function | 页面不存在监听函数 | 当小程序出现要打开的页面不存在的情况,会带上页面信息回调该函数| | 其他 | Any | | 开发者可以添加任意的函数或数据到 Object 参数中,用 this 可以访问| `getApp()`,全局的 getApp() 函数可以用来获取到小程序实例。 >[warning] 1. App() 必须在 app.js 中注册,且不能注册多个。 > 2. 不要在定义于 App() 内的函数中调用 getApp() ,使用 this 就可以拿到 app 实例。 > 3. 不要在 onLaunch 的时候调用 getCurrentPages(),此时 page 还没有生成。 > 4. 通过 getApp() 获取实例之后,不要私自调用生命周期函数。 #### 2.2.1.2 程序的生命周期和打开场景 * **前台、后台定义**: 当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。需要注意的是:只有当小程序进入后台一定时间,或者系统资源占用过高,才会被真正的销毁。 初次进入小程序的时候,微信客户端初始化好宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境,初始化完毕后,微信客户端就会给App实例派发onLaunch事件,App构造器参数所定义的`onLaunch`方法会被调用。 进入小程序之后,用户可以点击左上角的关闭,或者按手机设备的Home键离开小程序,此时小程序并没有被直接销毁,我们把这种情况称为“小程序进入后台状态”,App构造器参数所定义的`onHide`方法会被调用。 当再次回到微信或者再次打开小程序时,微信客户端会把“后台”的小程序唤醒,我们把这种情况称为“小程序进入前台状态”,App构造器参数所定义的`onShow`方法会被调用。 >[warning] App的生命周期是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,我们不应该从其他代码里主动调用App实例的生命周期函数。 在微信客户端中打开小程序有很多途径: * 从群聊会话里打开 * 从小程序列表中打开 * 通过微信扫一扫二维码打开 * 从另外一个小程序打开当前小程序等 针对不同途径的打开方式,小程序有时需要做不同的业务处理,所以微信客户端会把打开方式带给onLaunch和onShow的调用参数`options`,示例代码以及详细参数如代码清单3-5和表3-2所示。需要留意小程序的宿主环境在迭代更新过程会增加不少打开场景,因此要获取最新的场景值说明请查看[【官方文档】](https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/app.html)。 代码清单3-5 onLaunch和onShow带参数 ~~~ App({ onLaunch: function(options) { console.log(options) }, onShow: function(options) { console.log(options) } }) ~~~ :-: 表3-2 `onLaunch`,`onShow`参数 | 字段 | 类型 | 描述 | | --- |--- | ---| | path | String | 打开小程序的页面路径 | | query | Object | 打开小程序的页面参数query | | scene | Number | 打开小程序的场景值,详细场景值请参考小程序官方文档 | | shareTicket | String | shareTicket,详见小程序官方文档 | | referrerInfo | Object | 当场景为由从另一个小程序或公众号或App打开时,返回此字段 | | referrerInfo.appId | String | 来源小程序或公众号或App的 appId,详见下方说明 | | referrerInfo.extraData | Object | 来源小程序传过来的数据,scene=1037或1038时支持 | :-: 表3-3 以下场景支持返回 referrerInfo.appId | 场景值 | 场景 | appId信息含义 | | --- |--- | ---| | 1020 | 公众号 profile | 页相关小程序列表 返回来源公众号 appId | 1035 | 公众号自定义菜单 | 返回来源公众号 appId | 1036 | App 分享消息卡片 | 返回来源应用 appId | 1037 | 小程序打开小程序 | 返回来源小程序 appId | 1038 | 从另一个小程序返回 | 返回来源小程序 appId | 1043 | 公众号模板消息 | 返回来源公众号 appId `onPageNotFound` > 基础库 1.9.90 开始支持,低版本需做兼容处理 当要打开的页面并不存在时,会回调这个监听器,并带上以下信息: | 字段 | 类型 | 说明 | | --- |--- | ---| | path | String | 不存在页面的路径 | | query | Object | 打开不存在页面的 query | | isEntryPage | Boolean | 是否本次启动的首个页面 (例如从分享等入口进来,首个页面是开发者配置的分享页面)| 开发者可以在 `onPageNotFound` 回调中进行重定向处理,但必须在回调中同步处理,异步处理(例如 setTimeout 异步执行)无效。 示例代码: ~~~ App({ onPageNotFound(res) { wx.redirectTo({ url: 'pages/...' }) // 如果是 tabbar 页面,请使用 wx.switchTab } }) ~~~ >[warning] 1. 如果开发者没有添加 onPageNotFound 监听,当跳转页面不存在时,将推入微信客户端原生的页面不存在提示页面 > 2. 如果 onPageNotFound 回调中又重定向到另一个不存在的页面,将推入微信客户端原生的页面不存在提示页面,并且不再回调 onPageNotFound #### 2.2.1.3. 小程序全局数据 小程序的JS脚本是运行在JsCore的线程里,小程序的每个页面各自有一个WebView线程进行渲染,所以小程序切换页面时,小程序逻辑层的JS脚本运行上下文依旧在同一个JsCore线程中。 小程序的App实例是单例的,因此不同页面直接可以通过App实例下的属性来共享数据。 >[info] App构造器可以传递其他参数作为全局属性以达到全局共享数据的目的。 代码清单3-6 小程序全局共享数据 ~~~ // app.js App({ globalData: 'I am global data' // 全局共享数据 }) // 其他页面脚本other.js var appInstance = getApp() console.log(appInstance.globalData) // 输出: I am global data ~~~ >[danger] 由于所有页面的脚本逻辑都跑在同一个JsCore线程,页面使用setTimeout或者setInterval的定时器,然后跳转到其他页面时,这些定时器并没有被清除,需要开发者自己在页面离开的时候进行清理。 ### 2.2.2 页面 一个小程序可以有很多页面,每个页面承载不同的功能,页面之间可以互相跳转。 #### 2.2.2.1 页面的文件构成和路径 一个页面是分三部分组成: * 界面:`WXML`文件和`WXSS`文件描述 * 配置:由`JSON`文件进行描述 * 逻辑:`JS`脚本文件负责 >[danger] 一个页面的文件需要放置在同一个目录下,其中WXML文件和JS文件是必须存在的,JSON和WXSS文件是可选的。 >[danger] 页面路径需要在小程序代码根目录app.json中的pages字段声明,否则这个页面不会被注册到宿主环境中。 1. **全局配置** `app.json`文件用来对微信小程序进行全局配置,决定小程序各个页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。[【官方文档详细说明】](https://developers.weixin.qq.com/miniprogram/dev/framework/config.html) :-: app.json 配置项列表 | 属性 | 类型 | 必填 | 描述 | |--- | --- |--- | --- | | pages | String Array | 是 | 设置页面路径 | | window | Object | 否 | 设置默认页面的窗口表现 | | tabBar | Object | 否 | 设置底部 tab 的表现 | | networkTimeout | Object | 否 | 设置网络超时时间 | | debug | Boolean | 否 | 设置是否开启 debug 模式 | 代码清单3-7 app.json包含了所有配置选项。默认pages字段的第一个页面路径为小程序的首页。 ~~~ { "pages": [ "pages/index/index", // 第一项默认为首页 "pages/logs/index" ], "window": { "navigationBarTitleText": "Demo" }, "tabBar": { "list": [{ "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "日志" }] }, "networkTimeout": { "request": 10000, "downloadFile":10000 }, "debug": true } ~~~ 2. **页面配置** 每一个小程序页面使用`.json`文件来对本页面的窗口表现进行配置。 页面的配置比app.json全局配置简单得多,只是设置 app.json 中的 window 配置项的内容,页面中配置项会覆盖 app.json 的 window 中相同的配置项。 页面的.json只能设置 window 相关的配置项,以决定本页面的窗口表现,所以无需写 window 这个键,如: | 属性 | 类型 | 默认值 | 描述 | | --- | --- | --- | --- | | navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如"#000000" | | navigationBarTextStyle | String | white | 导航栏标题颜色,仅支持 black/white | | navigationBarTitleText | String | 导航栏标题文字内容 | | backgroundColor | HexColor | #ffffff | 窗口的背景色 | | backgroundTextStyle | String| dark | 下拉 loading 的样式,仅支持 dark/light | | enablePullDownRefresh | Boolean | false | 是否开启下拉刷新,详见页面相关事件处理函数。 | | disableScroll | Boolean | false | 设置为 true 则页面整体不能上下滚动;只在 page.json 中有效,无法在 app.json 中设置该项| | onReachBottomDistance | Number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px | 例如: ~~~ { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "微信接口功能演示", "backgroundColor": "#eeeeee", "backgroundTextStyle": "light" } ~~~ #### 2.2.2.2 页面构造器--`Page()` 宿主环境提供了` Page()` 构造器用来注册一个小程序页面,Page()在页面脚本`*.js`中调用,Page() 的调用方式如代码清单3-8所示。 Page构造器(`Page()`)接受一个Object参数,指定页面的初始数据、生命周期函数、事件处理函数等。参数说明如表3-4所示。 >[warning] Object 内容在页面加载时会进行一次深拷贝,需考虑数据大小对页面加载的开销 代码清单3-8 Page构造器 ~~~ Page({ data: { text: "This is page data." }, onLoad: function(options) { // Do some initialize when page load. }, onReady: function() { // Do something when page ready. }, onShow: function() { // Do something when page show. }, onHide: function() { // Do something when page hide. }, onUnload: function() { // Do something when page close. }, onPullDownRefresh: function() { // Do something when pull down. }, onReachBottom: function() { // Do something when page reach bottom. }, onShareAppMessage: function () { // return custom share data when user share. }, onPageScroll: function() { // Do something when page scroll }, onTabItemTap(item) { console.log(item.index) console.log(item.pagePath) console.log(item.text) }, // Event handler. viewTap: function() { this.setData({ text: 'Set some data for updating view.' }, function() { // this is setData callback }) }, customData: { hi: 'MINA' } }) ~~~ :-: 表3-4 Page构造器的参数 | 参数属性 | 类型 | 描述 | | --- |--- | --- | | data | Object | 页面的初始数据 | | onLoad | Function | 生命周期函数--监听页面加载,触发时机早于onShow和onReady | | onReady | Function | 生命周期函数--监听页面初次渲染完成 | | onShow | Function | 生命周期函数--监听页面显示,触发事件早于onReady | | onHide | Function | 生命周期函数--监听页面隐藏 | | onUnload | Function | 生命周期函数--监听页面卸载 | | onPullDownRefresh | Function | 页面相关事件处理函数--监听用户下拉动作 | | onReachBottom | Function | 页面上拉触底事件的处理函数 | | onShareAppMessage | Function | 用户点击右上角转发 | | onPageScroll | Function | 页面滚动触发事件的处理函数 | | onTabItemTap | Function | 当前是 tab 页时,点击 tab 时触发 | | 其他 | Any | 可以添加任意的函数或数据,在Page实例的其他函数中用 this 可以访问 | 上表中的Page构造器`page()`的参数可分为4部分 * ***初始化数据*** `data`属性是当前页面WXML模板中可以用来做数据绑定的初始数据;初始数据将作为页面的第一次渲染使用。 `data`将会以 JSON 的形式由逻辑层传至渲染层,所以其数据必须是可以转成 JSON 的格式:字符串,数字,布尔值,对象,数组。 渲染层可以通过 WXML 对数据进行绑定。 示例代码: ~~~HTML <!-- *.wxml --> <view>{{text}}</view> <view>{{array[0].msg}}</view> ~~~ ~~~JavaScript //*.js Page({ data: { text: 'init data', array: [{msg: '1'}, {msg: '2'}] } }) ~~~ * ***生命周期函数*** `onLoad / onReady / onShow / onHide /onUnload` 5个回调是Page实例的生命周期函数; 生命周期的触发以及页面的路由方式[【详见】](https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html) * ***事件处理函数*** 由用户行为触发的事件回调处理函数:`onPullDownRefresh / onReachBottom / onShareAppMessage / onPageScroll / onTabItemTap` 5个 * ***其他(用户自定义函数/组件事件处理函数)*** Page 中还可以用户自定义函数/组件事件处理函数。在渲染层的组件中加入事件绑定,当事件被触发时,就会执行 Page 中定义的事件处理函数。 示例代码: ~~~HTML <!-- // *.wxml --> <view bindtap="viewTap"> click me </view> ~~~ ~~~JavaScript // *.js Page({ viewTap: function() { console.log('view tap') } }) ~~~ #### 2.2.2.3 页面的生命周期和打开参数 :-: ![Page 实例的生命周期图](https://box.kancloud.cn/969257705be6d7c429e0ecb05ff2d9a8_384x602.png) :-: Page 实例的生命周期图 下述Page实例的5个生命周期函数/方法是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,我们不应该在其他代码中主动调用Page实例的生命周期函数。 **`onLoad(Object query)`**:页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数(参数名称:query,参数类型:Object)。 使用场景举例:购物商城的小程序,一个商品列表页和商品详情页,点击商品列表页的商品就可以跳转到该商品的详情页。 当然我们不可能为每个商品单独去实现它的详情页。我们只需要实现一个商品详情页的`pages/detail/detail`(代表WXML/WXSS/JS/JSON文件)即可,在列表页打开商品详情页时把商品的id传递过来,详情页通过刚刚说的onLoad回调的参数option就可以拿到商品id,从而绘制出对应的商品,代码如代码清单3-9所示。 代码清单3-9 Page构造器页面的打开参数 ~~~JavaScript // pages/list/list.js // 列表页使用navigateTo跳转到详情页 wx.navigateTo({ url: 'pages/detail/detail?id=1&other=abc' }) ~~~ ~~~JavaScript // pages/detail/detail.js Page({ onLoad: function(option) { console.log(option.id) console.log(option.other) } }) ~~~ 小程序把页面的打开路径定义成页面URL,其组成格式和网页的URL类似,在页面路径后使用英文` ?` 分隔path和query部分。 * query部分:多个参数使用 `&` 进行分隔,参数的名字和值使用 key=value 的形式声明。 在页面Page构造器里onLoad的option可以拿到当前页面的打开参数,其类型是一个Object,其键值对与页面URL上query键值对一一对应。 和网页URL一样,页面URL上的value如果涉及特殊字符(例如:`&`字符、`?`字符、`中文`字符等,详情参考URI的RFC3986说明 ),需要采用UrlEncode后再拼接到页面URL上。 **`onShow()`**:页面显示/切入前台时触发。 **`onReady()`**:页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。 >[warning] 对界面内容进行设置的 API 如wx.setNavigationBarTitle,请在onReady之后进行。 **`onHide()`**:页面隐藏/切入后台时触发。 如 `navigateTo` 或底部 `tab` 切换到其他页面,小程序切入后台等。 **`onUnload()`** :页面卸载时触发。如`redirectTo`或`navigateBack`到其他页面时。 * 页面生命周期函数调用过程说明 页面初次加载的时候,微信客户端就会给Page实例派发onLoad事件,Page构造器参数所定义的`onLoad`方法会被调用,onLoad在页面没被销毁之前只会触发1次,在onLoad的回调中,可以获取当前页面所调用的打开参数query。 页面显示之后,Page构造器参数所定义的`onShow`方法会被调用,一般从别的页面返回到当前页面时,当前页的onShow方法都会被调用。 在页面初次渲染完成时,Page构造器参数所定义的`onReady`方法会被调用,onReady在页面没被销毁前只会触发1次,onReady触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。 以上三个事件触发的时机是`onLoad`早于` onShow`,`onShow`早于`onReady`。 页面不可见时,Page构造器参数所定义的`onHide`方法会被调用,这种情况会在使用`wx.naviagteTo`切换到其他页面、底部tab切换时触发。 当前页面使用`wx.redirectTo`或`wx.navigateBack`返回到其他页时,当前页面会被微信客户端销毁回收,此时Page构造器参数所定义的`onUnload`方法会被调用。 #### 2.2.2.4 页面的数据 * `data`参数 小程序的页面结构由对应的`*.wxml`文件进行描述,可以通过数据绑定的语法绑定从逻辑层传递过来的数据字段(页面Page构造器的data参数中定义的字段),data参数是页面第一次渲染时从逻辑层传递到渲染层的数据。 * `setData`函数 宿主环境所提供的Page实例的原型中有`setData`函数,我们可以在Page实例下的方法调用`this.setData`把数据传递给渲染层,从而达到更新界面的目的。 由于小程序的渲染层和逻辑层分别在两个线程中运行,所以setData传递数据实际是一个异步的过程,所以setData的第二个参数是一个callback回调,在这次setData对界面渲染完毕后触发。 setData其一般调用格式是 `setData(data, callback)`,其中`data`是由多个`key: value`构成的Object对象。 代码清单3-11 使用setData更新渲染层数据 ~~~JavaScript // page.js Page({ onLoad: function(){ this.setData({ text: 'change data' }, function(){ // 在这次setData对界面渲染完毕后触发 }) } }) ~~~ 实际在开发的时候,页面的data数据会涉及相当多的字段,只需要把改变的值进行设置即可,宿主环境会自动把新改动的字段合并到渲染层对应的字段中。`data`中的key还可以非常灵活,还可以数据路径的形式给出,例如 `this.setData({"d[0]": 100})`; `this.setData({"d[1].text": 'Goodbye'})`; >[warning] * 提高小程序的渲染性能的原则:`每次只设置需要改变的最小单位数据`。 > * 直接修改 Page实例的this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致。 > * 由于`setData`是需要两个线程的一些通信消耗,为了提高性能,每次设置的数据不应超过`1024kB`。 > * 不要把`data`中的任意一项的value设为`undefined`,否则可能会有引起一些不可预料的bug。 代码清单3-12 使用setData更新渲染层数据 ~~~JavaScript // page.js Page({ data: { a: 1, b: 2, c: 3, d: [1, {text: 'Hello'}, 3, 4] } onLoad: function(){ // a需要变化时,只需要setData设置a字段即可 this.setData({a : 2}) } }) ~~~ #### 2.2.2.5 页面的用户行为 小程序宿主环境提供了5个和页面相关的用户行为回调: 1. **`onPullDownRefresh()`**:监听用户下拉刷新事件。 * 需要在app.json的window选项中或页面配置中开启`enablePullDownRefresh`。 * 可以通过`wx.startPullDownRefresh`触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。 * 当处理完数据刷新后,`wx.stopPullDownRefresh`可以停止当前页面的下拉刷新。 2. **`onReachBottom()`**:监听用户上拉触底事件。 * 可以在app.json的window选项中或页面配置中设置触发距离`onReachBottomDistance`。 * 在触发距离内滑动期间,本事件只会被触发一次。 3. **`onShareAppMessage(Object)`** :监听用户点击页面内转发按钮(`<button>` 组件 `open-type="share"`)或右上角菜单“转发”按钮的行为,并自定义转发内 容。 >[warning] 只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮 Object 参数说明: | 参数 | 类型 | 说明 | 最低版本 | | ---| ---| --- | --- | | from | String | 转发事件来源。`button`:页面内转发按钮;`menu`:右上角转发菜单 | 1.2.4 | | target | Object | 如果 from 值是 button,则 target 是触发这次转发事件的 button,否则为 undefined | 1.2.4 | | webViewUrl | String | 页面中包含<web-view>组件时,返回当前<web-view>的url | 1.6.4 | 此事件需要 return 一个 Object,用于自定义转发内容,返回内容如下: 自定义转发内容 | 字段 | 说明 | 默认值 | 最低版本 | | ---| ---| --- | --- | | title | 转发标题 | 当前小程序名称 | | | path | 转发路径 | 当前页面 path ,必须是以 / 开头的完整路径 | | | imageUrl | 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4。 | 使用默认截图 | 1.5.0 | 示例代码 ~~~JavaScript Page({ onShareAppMessage: function (res) { if (res.from === 'button') { // 来自页面内转发按钮 console.log(res.target) } return { title: '自定义转发标题', path: '/page/user?id=123' } } }) ~~~ 4. **`onPageScroll(Object)`**:监听用户滑动页面事件。 Object 参数说明: | 属性 | 类型 | 说明 | | ---| ---| --- | | scrollTop | Number | 页面在垂直方向已滚动的距离(单位px) | 5. **`onTabItemTap(Object)`**: > 基础库 1.9.0 开始支持,低版本需做兼容处理 点击 tab 时触发 Object 参数说明: | 参数 | 类型 | 说明 | 最低版本 | | ---| ---| --- | --- | | index | String | 被点击tabItem的序号,从0开始 | 1.9.0 | | pagePath | String | 被点击tabItem的页面路径 | 1.9.0 | | text | String | 被点击tabItem的按钮文字 | 1.9.0 | 示例代码: ~~~JavaScript Page({ onTabItemTap(item) { console.log(item.index) console.log(item.pagePath) console.log(item.text) } }) ~~~ * 用户自定义函数/组件事件处理函数 示例代码 ~~~HTML <!--index.wxml--> <view>{{text}}</view> <button bindtap="changeText"> Change normal data </button> <view>{{num}}</view> <button bindtap="changeNum"> Change normal num </button> <view>{{array[0].text}}</view> <button bindtap="changeItemInArray"> Change Array data </button> <view>{{object.text}}</view> <button bindtap="changeItemInObject"> Change Object data </button> <view>{{newField.text}}</view> <button bindtap="addNewField"> Add new data </button> ~~~ ~~~JavaScript //index.js Page({ data: { text: 'init data', num: 0, array: [{text: 'init data'}], object: { text: 'init data' } }, changeText: function() { // this.data.text = 'changed data' // bad, it can not work this.setData({ text: 'changed data' }) }, changeNum: function() { this.data.num = 1 this.setData({ num: this.data.num }) }, changeItemInArray: function() { // you can use this way to modify a danamic data path this.setData({ 'array[0].text':'changed data' }) }, changeItemInObject: function(){ this.setData({ 'object.text': 'changed data' }); }, addNewField: function() { this.setData({ 'newField.text': 'new data' }) } }) ~~~ #### 2.2.2.6 页面路由和跳转 在小程序中所有页面的路由全部由框架进行管理。 1. **页面栈** 一个小程序拥有多个页面,我们可以通过`wx.navigateTo`推入一个新的页面。在首页使用2次`wx.navigateTo`后,页面层级会有三层,我们把这样的一个页面层级称为`页面栈`。 页面栈描述:\[ pageA, pageB, pageC ],其中pageA在最底下,pageC在最顶上,也就是用户所看到的界面。小程序宿主环境限制了这个页面栈的最大层级为10层 ,也就是当页面栈到达10层之后就没有办法再推入新的页面了。 当发生路由切换的时候,页面栈的表现如下: | 路由方式 | 页面栈表现 | | --- | --- | | 初始化 | 新页面入栈 | | 打开新页面 | 新页面入栈 | | | 页面重定向 | 当前页面出栈,新页面入栈 | | 页面返回 | 页面不断出栈,直到目标返回页 | | Tab 切换 | 页面全部出栈,只留下新的 Tab 页面 | | 重加载 | 页面全部出栈,只留下新的页面 | 2. **getCurrentPages()** `getCurrentPages()` 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。 >[danger] 不要尝试修改页面栈,会导致路由以及页面状态错误。 3. **路由方式** 对于路由的触发方式以及页面生命周期函数如下: | 路由方式 | 触发时机 | 路由前页面 | 路由后页面 | | --- | --- | --- |---| | 初始化 | 小程序打开的第一个页面 | | onLoad, onShow | | 打开新页面 | 调用 API `wx.navigateTo` 或使用组件 `<navigator open-type="navigateTo"/>` | onHide | onLoad, onShow | | 页面重定向 | 调用 API `wx.redirectTo` 或使用组件 `<navigator open-type="redirectTo"/>` | onUnload | onLoad, onShow | | 页面返回 | 调用 API `wx.navigateBack` 或使用组件`<navigator open-type="navigateBack">`或用户按左上角返回按钮 | onUnload | onShow | | Tab 切换 | 调用 API `wx.switchTab` 或使用组件 `<navigator open-type="switchTab"/>` 或用户切换 Tab | | 各种情况请参考下表 | | 重启动 | 调用 API `wx.reLaunch` 或使用组件`<navigator open-type="reLaunch"/>` | onUnload | onLoad, onShow | Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例): | 当前页面 | 路由后页面 | 触发的生命周期(按顺序)| | --- | --- | --- | | A | A | 无 | | A | B | A.onHide(), B.onLoad(), B.onShow() | | A | B(再次打开) | A.onHide(), B.onShow() | | C | A | C.onUnload(), A.onShow() | | C | B | C.onUnload(), B.onLoad(), B.onShow() | | D | B | D.onUnload(), C.onUnload(), B.onLoad(), B.onShow() | | D(从转发进入) | A | D.onUnload(), A.onLoad(), A.onShow() | | D(从转发进入) | B | D.onUnload(), B.onLoad(), B.onShow() | 4. **和路由相关的API** * `wx.navigateTo({ url: 'pageD' })`: 可以往当前页面栈多推入一个 pageD,此时页面栈变成 \[ pageA, pageB, pageC, pageD ]。只能打开非TabBar页面。 * `wx.navigateBack()` 可以退出当前页面栈的最顶上页面,此时页面栈变成 \[ pageA, pageB, pageC ]。 * `wx.redirectTo({ url: 'pageE' })` 是替换当前页变成pageE,此时页面栈变成 \[ pageA, pageB, pageE ],当页面栈到达10层没法再新增的时候,往往就是使用redirectTo这个API进行页面跳转。只能打开非TabBar页面。 * `wx.switchTab`。页面栈中使用`wx.switchTab({ url: 'pageF' })`,此时原来的页面栈会被清空(除了已经声明为Tabbar页的pageA外其他页面会被销毁),然后会切到pageF所在的tab页面,页面栈变成 \[ pageF ],此时点击Tab1切回到pageA时,pageA不会再触发onLoad,因为pageA没有被销毁。 * `wx.reLaunch`。我们还可以使用`wx. reLaunch({ url: 'pageH' })` 重启小程序,并且打开pageH,此时页面栈为 \[ pageH ]。 小程序提供了原生的`Tabbar`支持,我们可以在`app.json`声明tabBar字段来定义Tabbar页(注:更多详细参数见Tabbar官方文档 )。 代码清单3-14 app.json定义小程序底部tab ~~~JavaScript { "tabBar": { "list": [ { "text": "Tab1", "pagePath": "pageA" }, { "text": "Tab1", "pagePath": "pageF" }, { "text": "Tab1", "pagePath": "pageG" } ] } } ~~~ >[warning] * `wx.navigateTo`, `wx.redirectTo` 只能打开非 tabBar 页面。 > * `wx.switchTab` 只能打开 tabBar 页面。 > * `wx.reLaunch` 可以打开任意页面。 > * 页面底部的 `tabBar` 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。 > * 调用页面路由带的参数可以在目标页面的`onLoad`中获取。 5. **路由信息 `Page.prototype.route`** >基础库 1.2.0 开始支持,低版本需做兼容处理 route 字段可以获取到当前页面的路径。 ~~~JavaScript //index.js Page({ onShow:function(){ console.log(this.route) } }) ~~~