《微信小程序:开发入门及案例详解》—— 2.4 框架页面文件

本节书摘来自华章出版社《微信小程序:开发入门及案例详解》一 书中的第2章,第2.4节,作者李骏 边思,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.4 框架页面文件

小程序中一个框架页面包含4个文件,同一框架页面的这4个文件必须具有相同的路径与文件名,进入小程序时或页面跳转时,小程序会根据app.json配置的路径找到对应的资源进行渲染。
◇ .js文件:页面逻辑文件,必要项。
◇ .wxml文件:页面结构文件,必要项。
◇ .wxss文件:页面样式文件。
◇ .json文件:页面配置文件。
与框架主体文件相比框架页面文件多了一种页面结构文件,其余3个文件和框架主体文件的功能类同,下面我们一一讲解每个文件作用。

2.4.1 页面配置文件

我们首先讲解页面配置文件,页面配置文件和框架配置文件一样,是一个json文件,与框架配置文件不同的是,页面配置文件是非必要存在的,同时页面配置文件的配置项只有window,控制当前页面的窗口表现,window的属性和app.json一致。渲染页面时,页面中的window配置项会覆盖app.json中的相同配置项。
由于页面的.json只能配置window相关属性,编写时只需直接写出属性,不用写window这个键,如下所示:

{
  "navigationBarBackgroundColor": "#000000",
  "navigationBarTextStyle": "black",
  "navigationBarTitleText": "我的页面",
  "backgroundColor": "#efefef",
  "backgroundTextStyle": "light"
}

2.4.2 页面逻辑文件(JavaScript)

页面逻辑文件主要功能有:设置初始化数据,注册当前页面生命周期函数,注册事件处理函数等。小程序的逻辑层文件是JavaScript文件,所有的逻辑文件,包括app.js,最终将会打包成一个js文件,在小程序启动时运行,直到小程序销毁,类似于ServiceWorker,所以逻辑层也称为App Service。
在小程序中,每个逻辑文件有独立的作用域,并具备模块化能力。由于JavaScript逻辑文件是运行在纯JavaScript引擎中而并非运行在浏览器中,一些浏览器提供的特有对象,如document、window等,在小程序中都无法使用,同理,一些基于document、window的框架如:jQuery、Zepto都不能在小程序中使用,同时我们不能通过操作DOM改变页面,这时需要我们将面向DOM操作的编程思路转化为数据绑定和事件响应。

1.注册页面

在页面逻辑文件中需要通过Page()函数注册页面,指定页面的初始数据、生命周期函数、事件处理函数等,参数为一个Object对象,其属性如下:
◆ data:页面的初始数据,数据格式必须是可转成JSON格式的对象类型。当页面第一次渲染时,data会以JSON的形式由逻辑层传至渲染层,渲染层可以通过WXML对数据进行绑定。
◆ onLoad:生命周期函数,页面加载时触发。一个页面只会调用一次,接受页面参数,可以获取wx.navigateTo、wx.redirectTo以及中的query参数。
◆ onShow:生命周期函数,页面显示时触发。每次打开页面都会调用一次。
◆ onReady:生命周期函数,页面初次渲染完成时触发。一个页面生命周期中只会调用一次,代表当前页面已经准备妥当,可以和视图层进行交互。一些对界面的设置,操作需要在页面准备妥当后调用,如wx.setNavigationBarTitle需要在onReady之后设置,详细可以参考后面的“页面生命周期”小节。
◆ onHide:生命周期函数,页面隐藏时触发。
◆ onUnload:生命周期函数,页面卸载时触发。
◆ onPullDownRefresh:页面相关时间处理函数,用户下拉时触发。使用时需要将app.json配置中window的enablePullDownRefresh属性设置为true。当处理完数据刷新后,可以调用wx.stopPullDownRefresh方法停止当前页面的下拉刷新。
◆ onReachBottom:页面上拉触底事件的处理函数。
◆ 其他:开发者可以添加任意的函数或数据到Object参数中,可以用this访问这些函数和数据。示例代码如代码清单2-2所示:
代码清单2-2 页面逻辑文件

// 获取app实例
var app = getApp();
Page( {
  data : {    // 页面初始化数据
    count : 0
  },
  onLoad : function() {
    // 页面加载时执行
  },
  onShow : function() {
      // 页面打开时执行
      console.log( app.globalData );
  },
  onReady : function() {
      // 页面初次渲染完成执行,一个页面只会调用一次
  },
  onHide : function() {
      // 页面隐藏时执行
  },
  onUnload : function() {
      // 页面卸载时执行
  },
  onPullDownRefresh : function() {
      // 下拉刷新时执行
  },
  onReachBottom : function() {
      // 下拉触底时执行
  },
  // 自定义函数,可与渲染层中的组件进行事件绑定
  countClick : function() {
    // 触发视图层重新渲染
    this.setData( {
      count : this.data.count + 1
    } );
  },
  // 自定义数据
  customData : {
    name : '微信'
  }
} );

页面的生命周期函数比小程序的生命周期函数略微复杂一点,弄懂其执行顺序能避免在不恰当的生命周期函数中调用还未创建的对象或方法,小程序框架以栈的形式维护了当前的所有页面,当发生路由切换时,页面栈和生命周期函数的关系如下:
◆ 小程序初始化:默认页面入栈,依次触发默认页面onLoad、onShow、onReady方法。
◆ 打开新页面:新页面入栈,依次触发新页面onLoad、onShow、onReady方法。
◆ 页面重定向:当前页面出栈并卸载,触发当前页面onUnload方法,新页面入栈,触发新页面onLoad、onShow、onReady方法。
◆ 页面返回:页面不断出栈并卸载,触发当前弹出页面onUnload方法,直到返回目标页面,新页面入栈,触发新页面onShow方法。
◆ Tab切换:当前页面出栈但不卸载,仅触发onHide方法,新页面入栈,如果当前页面是新加载的,触发onLoad、onShow、onReady方法,如果当前页面已加载过,仅触发onShow方法。
◆ 程序从前台到后台:触发当前页面onHide方法,触发App onHide方法。
◆ 程序从后台到前台:触发小程序onShow方法,触发页面onShow方法。
整体来说,如果页面在生命周期中,只会触发onShow和onLoad方法,只有加载和卸载时才会触发onLoad、onReady和onUnload方法,而触发页面卸载只有页面返回和页面重定向两种操作。页面生命周期函数的执行顺序不用死记硬背,开发过程中可以开启app.json中的debug模式,路由切换时,小程序、页面的生命周期函数执行顺序都会在控制台以info形式输出,在后面小节中我们将对页面生命周期作简单介绍。

2. 获取当前页面栈

有注册就有获取,getCurrentPages()函数便是用于获取当前页面栈的实例,页面栈以数组形式按栈顺序给出,第一个元素为首页,最后一个元素为当前页面。不要尝试修改页面栈,这会导致路由以及页面状态错误。
示例代码如下:

/* 获取页面栈 */
var pages = getCurrentPages();
/* 获取当前页面对象 */
var currentPage = pages[pages.length - 1];
3. 事件处理函数

页面对象中注册的函数可以和视图层中的组件进行绑定,当达到触发条件时,就会执行Page中定义的相应事件,这类自定义函数统称为事件处理函数。小程序中组件的事件分为通用事件和特殊事件,事件类型请参考后面2.4.3节中“事件”小节。
示例代码如下:

点击执行逻辑层事件

Page( {
  myevent : function() {
    console.log( '点击了view' );
  }
} );
4. 触发视图层渲染

页面首次加载时,框架会结合初始化数据渲染页面,在逻辑层中则需主动调用Page.
prototype.setData()方法,而不能直接修改Page的data值,这样不仅无法触发视图层渲染,还会造成数据不一致。当 Page.prototype.setData()被调用时,会将数据从逻辑层发送到视图层触发视图层重绘,同时会修改Page的data值。setData()接受一个Object对象参数,方法会自动将this.data中的key对应的值变成Object参数中key对应的值。当Object参数key对应的值和this.data中key对应的值一致时,将不会触发视图层渲染。在项目中我们一定要保证视图层和逻辑层的数据一致。
Object参数的key值非常灵活,可以按数据路径的形式给出,如array[5].info、obj.key.subkey,并且这样使用时,不需要在this.data中预先定义。
示例代码如下:

{{text}}


{{object.subObject.objectText}}


{{array[0].arrayText}}


{{newField.newFieldText}}

Page( {
  data : {
    text : 'normal data',
    object : {
      subObject : {
        objectText : 'object data'
      }
    },
    array : [
      { arrayText : 'array data' }
    ]
  },
  changeText : function() {
    this.setData( {
      /* 普通索引 */
      text : 'new normal data'
    } );
  },
  changeObjectText : function() {
    this.setData( {
      /* 按路径索引 */
      'object.subObject.objectText' : 'new object data'
    } );
  },
  changeArrayText : function() {
    this.setData( {
      /* 按路径索引 */
      'array[0].arrayText' : 'new array data'
    } );
  },
  addNewData : function() {
    this.setData( {
      /* 修改一个已绑定,但未在data中定义的数据 */
      'newField.newFieldText' : 'add new data'
    } );
  }
} );
5. 页面生命周期

页面的生命周期整体关系着页面视图层线程和页面逻辑层线程,注册页面时,Object参数中很多属性都是生命周期函数,这些函数的调用和页面生命息息相关,程序视图层线程和逻辑层线程关系如图2-8所示。

《微信小程序:开发入门及案例详解》—— 2.4 框架页面文件_第1张图片

如图2-8,线程启动后视图层和逻辑层相互监听,当逻辑层线程触发onLoad、onShow方法后会把初始数据data传送给视图层线程,视图层完成第一次渲染后触发逻辑层onReady方法,代表页面已经准备妥当,之后我们便可通过setData方法主动触发视图层渲染。当页面被调往后台时,触发onHide方法,这时逻辑层线程并没有销毁,我们仍然可以通过代码控制视图层渲染,只是可能不会在界面上表现出来。当页面从后台回到前台时,触发onShow方法,最后当页面销毁时,触发onUnload方法。整体来看onLoad、onReady和onUnload方法在生命周期中只会调用一次,生命周期内显示、隐藏页面都是触发onShow和onHide方法,在路由方式中,只有页面重定向和页面返回会结束当前页面生命周期,当进入一个已加载的页面时只会触发onShow方法,不会触发onLoad和onReady方法。

2.4.3 页面结构文件(WXML)

WXML(WeiXin Markup Language)是框架设计的一套标记语言,用于渲染界面,WXML的渲染原理和React Native思路一致,通过一套标记语言,在不同平台被解析为不同端的渲染文件,如图2-9所示。

《微信小程序:开发入门及案例详解》—— 2.4 框架页面文件_第2张图片

从图中我们能看出,WXML语言最终会转译为宿主端对应的语言,所以WXML中所使用的标签一定是小程序定义的标签,不能使用自定义标签,这样才能保证页面能被正确转译。使用微信开发者工具开发时,在WXML中编写一些HTML标签或自定义标签仍然会被正常解析,这会给开发者造成一种小程序能直接支持HTML标签的误解。这是因为微信开发者工具内核是浏览器内核,同时小程序框架并没对WXML中的标签和WXSS中的内容进行强验证,所以HTML和CSS能直接被解析,但这种不合法的WXML在手机端微信中是不能正常显示的。开发过程中我们一定要拿真机进行测试,保证程序能正常运行。
WXML具有数据绑定、列表渲染、条件渲染、模板、事件等能力。

1. 数据绑定

小程序中页面渲染时,框架会将WXML文件同对应Page的data进行绑定,在页面中我们可以直接使用data中的属性。小程序的数据绑定使用Mustache语法(双大括号)将变量或简单的运算规则包起来,主要有以下几种渲染方式。

(1)简单绑定

简单绑定是指我们使用Mustache语法(双大括号)将变量包起来,在模板中直接作为字符串输出使用,可作用于内容、组件属性、控制属性、关键字等输出,其中关键字输出是指将JavaScript中的关键字按其真值输出。
示例代码如下:


{{content}}


作为属性渲染


作为属性渲染


{{2}}


Page( {
  data : {
    border : 'solid 1px #000',
    id : 1,
    content : '内容',
    showContent : false
  }
} );

运行效果如图2-10所示。


《微信小程序:开发入门及案例详解》—— 2.4 框架页面文件_第3张图片
(2)运算

在{{}}内可以做一些简单的运算,支持的运算有三元运算、算数运算、逻辑判断、字符串运算,这些运算均符合JavaScript运算规则。我们利用如下示例为大家展示:


{{ showContent ? '显示文本' : '不显示文本'}}

{{ num1 + num2 }} + 1 + {{ num3 }} = ? 


{{ "name : " + name }}


{{ num3 > 0 }}


{{ myObject.age }} {{myArray[1]}}

Page( {
  data : {
    showContent : false,
    num1 : 1,
    num2 : 2,
    num3 : 3,
    name : 'weixin',
    myObject : {
      age : 12
    },
    myArray : ['arr1','arr2']
  }
} );

执行后界面如图2-11所示。

《微信小程序:开发入门及案例详解》—— 2.4 框架页面文件_第4张图片
(3)组合

data中的数据可以在模板再次组合成新的数据结构,这种组合常常在数组或对象中使用。
数组组合比较简单,可以直接将值放置到数组某个下标下:

{{ [myValue, 2, 3, 'stringtype'] }}

Page( {
  data : {
    myValue : 0
  }
} );

最终页面组合成的对象为[0, 2, 3, 'stringtype']。
对象组合有3种组合方式,这里我们以数据注入模板为例。
第一种,直接将数据作为value值进行组合:



Page( {
  data : {
    myValue1 : 'value1',
    myValue2 : 'value2'
  }
} );

最终组合出的对象为{ name : 'value1', age : 'value2' }。
第二种,通过“…”将一个对象展开,把key-value值拷贝到新的结构中:



Page( {
  data : {
    myObj1 : {
      key1 : 1,
      key2 : 2
    },
    myObj2 : {
      key3 : 3,
      key4 : 4
    }
  }
} );

最终组合成的对象为{ key1 : 1, key2 : 2, key5 : 5, key3 : 3 key4 : 4, key6 : 6 }
第三种,如果对象key和value相同,可以只写key值:



Page( {
  data : {
    key1 :1,
    key2 : 2
  }
} );

这种写法最后组合成的对象是{ key1 : 1, key2 : 2 }
上述3种方式可以根据项目灵活组合,要注意的是和js中的对象一样,如果一个组合中有相同的属性名时,后面的属性将会覆盖前面的属性,如: