1. 主体文件结构
主体部分由三个文件组成,必须放在项目的根目录,如下:
| 文件 | 必填 | 作用 |
|---|---|---|
| app.js | 是 | 小程序逻辑 |
| app.json | 是 | 小程序公共设置( 严格的JSON语法,不能用单引号,不能写注释,否则编译报错 ) |
| app.wxss | 否 | 小程序公共样式表( 拓展了rpx尺寸单位,微信专属响应式像素 ) |
2. 页面文件结构
页面由四个文件组成,分别是:
| 文件类型 | 必填 | 作用 |
|---|---|---|
| js | 是 | 页面逻辑 ( 微信小程序没有window和document对象 ) |
| wxml | 是 | 页面结构 ( XML语法,不是HTML语法 ) |
| wxss | 否 | 页面样式表 ( 拓展了rpx尺寸单位,微信专属响应式像素 ) |
| json | 否 | 页面配置 ( 严格的JSON语法,不能用单引号,不能写注释,否则编译报错 ) |
3. 其他文件
project.config.json : 项目的配置文件
sitemap.json : 用来配置小程序及其页面是否允许被微信索引(上线的时候使用)
{
"rules":[{
"action": "allow",
"page": "path/to/page"
}, {
"action": "disallow",
"page": "*"
}]
}
小程序启动过程:
1.把小程序的代码包下载到本地
2.解析app.json全局配置文件
3.执行app.js小程序入口文件,调用App()创建小程序实例
4.渲染小程序首页
5.小程序启动完成
页面渲染过程
1.加载解析页面的.json配置文件
2.加载页面的.wxml模板和.wxss样式
3.执行页面的.js文件,调用Page()创建页面实例
4.页面渲染完成
2. 微信小程序-app.json
1. app.json 配置项列表
https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html
{
"pages": [
"pages/index/index",
"pages/logs/index"
],
"window": {
"navigationBarTitleText": "Demo"
},
"tabBar": {
"color": "#dddddd",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "page/component/index",
"iconPath": "image/icon_component.png",
"selectedIconPath": "image/icon_component_HL.png",
"text": "组件"
}, {
"pagePath": "page/API/index/index",
"iconPath": "image/icon_API.png",
"selectedIconPath": "image/icon_API_HL.png",
"text": "接口"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true,
"navigateToMiniProgramAppIdList": [
"wxe5f52902cf4de896"
]
}
//如果要新增页面可以直接在pages里面编辑
app.json文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
| pages | String Array | 是 | 设置页面路径 |
| window | Object | 否 | 设置默认页面的窗口表现 |
| tabBar | Object | 否 | 设置底部 tab 的表现 |
| debug | Boolean | 否 | 设置是否开启 debug 模式 |
| style | 设置样式版本 |
2 .window配置
用于设置小程序的状态栏、导航条、标题、窗口背景色。
注意:页面的
.json只能设置window相关的配置项,以决定本页面的窗口表现,所以无需写window这个键。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如”#000000” |
| navigationBarTextStyle | String | white | 导航栏标题颜色,仅支持 black/white |
| navigationBarTitleText | String | 导航栏标题文字内容 | |
| backgroundColor | HexColor | #ffffff | 窗口的背景色,可以通过开启 “enablePullDownRefresh”:true来查看窗口颜色 |
| backgroundTextStyle | String | dark | 下拉刷新的背景字体、loading 图的样式,仅支持 dark/light |
| enablePullDownRefresh | Boolean | false | 是否开启下拉刷新,详见页面相关事件处理函数。 |
| onReachBottomDistance | Number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px |
3. tabBar
https://mp.weixin.qq.com/debug/wxadoc/dev/framework/config.html#tabbar
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
Tip:
- 当设置 position 为 top 时,将不会显示 icon。
- tabBar 中的 list 是一个数组,只能配置最少2个、最多5个 tab,tab 按数组的顺序排序。
属性说明:
| 属性 | 类型 | 必填 | 默认值 | 描述 |
|---|---|---|---|---|
| color | HexColor | 是 | tab 上的文字默认颜色 | |
| selectedColor | HexColor | 是 | tab 上的文字选中时的颜色 | |
| backgroundColor | HexColor | 是 | tab 的背景色 | |
| borderStyle | String | 否 | black | tabbar上边框的颜色, 仅支持 black/white |
| list | Array | 是 | tab 的列表,详见 list 属性说明,最少2个、最多5个 tab | |
| position | String | 否 | bottom | 可选值 bottom、top,设置成top是无图标 |
其中 list 接受一个数组,数组中的每个项都是一个对象,其属性值如下:
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pagePath | String | 是 | 页面路径,必须在 pages 中先定义 |
| text | String | 是 | tab 上按钮文字 |
| iconPath | String | 否 | 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,当 postion 为 top 时,此参数无效 |
| selectedIconPath | String | 否 | 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px ,当 postion 为 top 时,此参数无效 |
4.页面配置–.json
在页面配置中,可以修改全局配置的部分信息,用来指定当前页面的一些特有配置
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如”#000000” |
| navigationBarTextStyle | String | white | 导航栏标题颜色,仅支持 black/white |
| navigationBarTitleText | String | 导航栏标题文字内容 | |
| backgroundColor | HexColor | #ffffff | 窗口的背景色,可以通过开启 “enablePullDownRefresh”:true来查看窗口颜色 |
| backgroundTextStyle | String | dark | 下拉刷新的背景字体、loading 图的样式,仅支持 dark/light |
| enablePullDownRefresh | Boolean | false | 是否开启下拉刷新,详见页面相关事件处理函数。 |
| onReachBottomDistance | Number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px |
| disableScroll | Boolean | false | 设置为true则页面整体不能上下滚动,只在页面配置中有效,无法在app.json中设置该项 |
3.wxml语法
| wxml | html | |
|---|---|---|
| 标签名称 | view,text,image,navigator | div,span,img,a |
| 属性节点 | <a href="#">超链接</a> |
<navigator url="/apges/home/home">超链接</navigator> |
4.样式
与 CSS 相比,WXSS 扩展的特性有:
尺寸单位(rpx rem)
vw和vh是css3中的新单位,是一种视窗单位,在小程序中同样适用
样式导入
提供了全局的样式和局部样式(项目根目录下的app.wxss会作用于所有小程序页面,局部页面的.wxss样式仅对当前页面生效)
wxss仅支持部分css选择器
1.尺寸单位
1.rpx(responsive pixel):
可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
2.vw和vh:
是css3中的新单位,是一种视窗单位,在小程序中也同样适用。
小程序中,窗口宽度固定为100vw,将窗口宽度平均分成100份,1份是1vw
小程序中,窗口高度固定为100vh ,将窗口高度平均分成100份,1份是1vh
2.样式引入
使用
@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。/** common.wxss **/ .small-p { padding:5px; } /** app.wxss **/ @import "common.wxss"; .middle-p { padding:15px; } .box2{ border: 1px solid red; width: 600rpx; height: 600rpx; /*background-image可以指定网络图片,但是不可以指定本地图片*/ background-image: url("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586254613140&di=33474e16f9fc5342f7a1e2be72f287f6&imgtype=0&src=http%3A%2F%2Fdmimg.5054399.com%2Fallimg%2Fpkm%2Fpk%2F22.jpg"); /* background-image: url(../../icons/timg.jpg); */ }
3.选择器
特别需要注意的是小程序 不支持通配符 * ,因此以下代码无效!
*{
margin:0;
padding:0;
box-sizing:border-box;
}
目前支持的选择器有:
| 选择器 | 样例 | 描述 |
|---|---|---|
| .class | .container | 选择所有拥有class=”container”的组件 |
| #id | #main | 选择拥有id=”main”的组件 |
| element | view | 选择所有view组件 |
| element,element | view,text | 选择所有文档的view组件和所有text组件 |
| element element | view text | 选择view 下的所有text组件 |
| ::after | view::after | 在view组件后边插入内容 |
| ::before | view::before | 在view组件前边插入内容 |
| nth-child(n) | view:nth-child(n) | 选择某个索引的标签 |
4.小程序中使用less
原声小程序不支持 less ,其他基于小程序的框架基本都支持,如 wepy , mpvue , taro ,uni-app等。
但是仅仅因为一个less功能,而去引用框架,肯定是不可取的。因此可以使用以下方式来实现
1. 编辑器是 vscode
2. 安装插件 easy less
3. 在vs code的设置中加如下,配置
{
"workbench.sideBar.location": "left",
"less.compile": {
"outExt":".wxss"
}
}
- 在要编写样式的地方新建 less 文件,如 index.less ,然后正常编辑即可。
5.js文件分类
1.app.js:
是整个小程序项目的入口文件,通过调用App()函数来启动整个小程序
2.页面的.js文件:
是页面的入口文件,通过调用Page()函数来创建并运行页面
3.普通的.js文件:
普通的功能模块文件,用来封装公共的函数或属性供页面使用
4.小程序不是运行在浏览器中,所以没有window和document对象
1.小程序不是运行在浏览器中,所以没有window和document对象
console.log(window);
console.log(document);
2.小程序js的一些额外成员
App({})方法:用于创建应用程序实例,指定应用程序的生命周期
Page()方法:用于创建页面对象
getApp()方法:用于获取全局的应用程序对象
getCurrentPages()方法:用于获取当前页面的调用栈
# https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html
wx : 微信小程序获取API的对象
5.import 和 include(小程序支持CommonJS规范)
1.小程序支持CommonJS规范
exports.aa 或者module.exports导出
require 引入
<import src="a.wxml"/> 作用可以引入目标文件中定义的template,然后在当前的wxml中使用template。注意点不能使用引入目标文件中所引入的第三方的template,比如A import B,B import C,A可以使用B中声明的template,但是A不可以使用C中声明的template
<include src="header.wxml"/> 作用可以引入目标文件中除了template wxs之外的所有代码
wxs语法
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。
wxml中无法调用在页面的.js中定义的函数,但是可以调用wxs中定义的函数。WXS存在的意义就是充当computed/filter这类功能。经常配合Musttache语法进行使用,但是不能作为组件的事件回调函数
wxs有自己的数据类型,wxs不支持es6语法,并且遵循CommonJS规范
WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。
WXS 代码可以编写在 wxml 文件中的 <wxs> 标签内,或以 .wxs 为后缀名的文件内。
wxs的运行环境和其他JavaScript代码是隔离的,wxs中不能调用js中定义的函数,也不能调用小程序中提供的API
在IOS设备上,小程序内的WXS会比JavaScript代码快2-20倍,但在android设备上,两者运行效率无差异
https://developers.weixin.qq.com/miniprogram/dev/reference/wxs/
<!-- 声明一个wxs模块 : 不支持es6 需要指定模块的名字 module,方便在wxml中访问模块中的成员 -->
<wxs module="dateFormat">
//准备一个时间格式化的方法
var format = function(date){
//根据时间戳使用小程序提供的getDate()方法得到一个时间
var time = getDate(date);
var year = time.getFullYear();
var month = time.getMonth() + 1;
var date = time.getDate();
var hour = time.getHours();
var minute = time.getMinutes();
var second = time.getSeconds();
month = month < 10 ? "0" + month : month;
date = date < 10 ? "0" + date : date;
hour = hour < 10 ? "0" + hour : hour;
minute = minute < 10 ? "0" + minute : minute;
second = second < 10 ? "0" + second : second;
return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
};
var format2 = function(msg){
var reg = getRegExp('小', "g");
return msg.replace(reg, "大");
}
var msg = "hello world";
//将外部需要使用的信息导出
module.exports = {
format : format,
format2:format2
msg:msg
}
</wxs>
<text>{{dateFormat.format(now)}}</text>
<text>{{dateFormat.format2("小小的")}}</text>
<text>{{dateFormat.msg}}</text>
使用外联的wxs脚本
1.必须为<wxs>标签添加module和src属性
2.module用来指定模块的名称
3.src用来指定要引入的脚本的路径,且必须是相对路径
utils/tools.wxs
var toLower = function (str) {
return str.toLowerCase();
};
module.exports = {
toLower: toLower,
};
index.wxml中
<view>
{{m2.toLower('HH')}}
</view>
<wxs module="m2" src="../../utils/index.wxs" />
5.wxml语法参考
1.数据绑定
WXML 中的动态数据均来自对应 Page 的 data。
简单绑定
数据绑定使用 Mustache 语法(双大括号)将变量包起来,可以作用于:
内容
<view> {{ message }} </view>
Page({
data: {
message: 'Hello MINA!'
}
})
组件属性(需要在双引号之内)
<view src="{{imgurl}}"> </view>
Page({
data: {
imgurl: '/assets/imgs/icon-daokuanrenling.png'
}
})
控制属性(需要在双引号之内)
<view wx:if="{{condition}}"> </view>
Page({
data: {
condition: true
}
})
关键字(需要在双引号之内)
true:boolean 类型的 true,代表真值。
false: boolean 类型的 false,代表假值。
特别注意:不要直接写 checked=”false”,其计算结果是一个字符串,转成 boolean 类型后代表真值。
<checkbox checked="{{false}}"> </checkbox>
三元运算
<view hidden="{{flag ? true : false}}"> Hidden </view>
算数运算
<view> {{a + b}} + {{c}} + d </view>
Page({
data: {
a: 1,
b: 2,
c: 3
}
})
view中的内容为 3 + 3 + d。
逻辑判断
<view wx:if="{{length > 5}}"> </view>
字符串运算
<view>{{"hello" + name}}</view>
Page({
data:{
name: 'MINA'
}
})
数据路径运算
<view>{{object.key}} {{array[0]}}</view>
Page({
data: {
object: {
key: 'Hello '
},
array: ['MINA']
}
})
组合
也可以在 Mustache 内直接进行组合,构成新的对象或者数组。
数组
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view>
Page({
data: {
zero: 0
}
})
最终组合成数组[0, 1, 2, 3, 4]。
对象
<template is="objectCombine" data="{{for: a, bar: b}}"></template>
Page({
data: {
a: 1,
b: 2
}
})
最终组合成的对象是 {for: 1, bar: 2}
也可以用扩展运算符 ... 来将一个对象展开
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
c: 3,
d: 4
}
}
})
最终组合成的对象是 {a: 1, b: 2, c: 3, d: 4, e: 5}。
如果对象的 key 和 value 相同,也可以间接地表达。
<template is="objectCombine" data="{{foo, bar}}"></template>
Page({
data: {
foo: 'my-foo',
bar: 'my-bar'
}
})
最终组合成的对象是 {foo: 'my-foo', bar:'my-bar'}。
注意:上述方式可以随意组合,但是如有存在变量名相同的情况,后边的会覆盖前面,如:
<template is="objectCombine" data="{{...obj1, ...obj2, a, c: 6}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
b: 3,
c: 4
},
a: 5
}
})
最终组合成的对象是 {a: 5, b: 3, c: 6}。
注意: 花括号和引号之间如果有空格,将最终被解析成为字符串
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
等同于
<view wx:for="{{[1,2,3] + ' '}}">
{{item}}
</view>
列表渲染
wx:for
在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
使用 wx:for-item 可以指定数组当前元素的变量名,
使用 wx:for-index 可以指定数组当前下标的变量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
wx:for 也可以嵌套,下边是一个九九乘法表
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i">
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j">
<view wx:if="{{i <= j}}"> {{i}} * {{j}}="{{i" j}} < view> < code>=>
block wx:for
类似 block wx:if,也可以将 wx:for 用在<block/>标签上,以渲染一个包含多节点的结构块。例如:
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
wx:key
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 input 中的输入内容,switch 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符。(不需要用插值语法表示,直接使用即可)
wx:key 的值以两种形式提供
- 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
- 保留关键字
*this代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。
如不提供 wx:key,会报一个 warning, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。
示例代码
<switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch>
<button bindtap="switch"> Switch </button>
<button bindtap="addToFront"> Add to the front </button>
<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>
<button bindtap="addNumberToFront"> Add to the front </button>
Page({
data: {
objectArray: [
{id: 5, unique: 'unique_5'},
{id: 4, unique: 'unique_4'},
{id: 3, unique: 'unique_3'},
{id: 2, unique: 'unique_2'},
{id: 1, unique: 'unique_1'},
{id: 0, unique: 'unique_0'},
],
numberArray: [1, 2, 3, 4]
},
switch: function(e) {
const length = this.data.objectArray.length
for (let i = 0; i < length; ++i) {
const x = Math.floor(Math.random() * length)
const y = Math.floor(Math.random() * length)
const temp = this.data.objectArray[x]
this.data.objectArray[x] = this.data.objectArray[y]
this.data.objectArray[y] = temp
}
this.setData({
objectArray: this.data.objectArray
})
},
addToFront: function(e) {
const length = this.data.objectArray.length
this.data.objectArray = [{id: length, unique: 'unique_' + length}].concat(this.data.objectArray)
this.setData({
objectArray: this.data.objectArray
})
},
addNumberToFront: function(e){
this.data.numberArray = [ this.data.numberArray.length + 1 ].concat(this.data.numberArray)
this.setData({
numberArray: this.data.numberArray
})
}
})
注意事项
当 wx:for 的值为字符串时,会将字符串解析成字符串数组
<view wx:for="array">
{{item}}
</view>
等同于
<view wx:for="{{['a','r','r','a','y']}}">
{{item}}
</view>
注意: 花括号和引号之间如果有空格,将最终被解析成为字符串
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
等同于
<view wx:for="{{[1,2,3] + ' '}}" >
{{item}}
</view>
条件渲染
wx:if
在框架中,使用 wx:if="" 来判断是否需要渲染该代码块:
<view wx:if="{{condition}}"> True </view>
也可以用 wx:elif 和 wx:else 来添加一个 else 块:
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
block wx:if
因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 <block/> 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
注意: <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
wx:if vs hidden
hidden: 为true时隐藏,为false时显示
因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。
同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。
一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。
模板
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
定义模板
使用 name 属性,作为模板的名字。然后在<template/>内定义代码片段,如:
<!--
index: int
msg: string
time: string
-->
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
使用模板
使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入,如:
<template is="msgItem" data="{{...item}}"/>
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
is 属性可以使用 Mustache 语法,来动态决定具体需要渲染哪个模板:
<template name="odd">
<view> odd </view>
</template>
<template name="even">
<view> even </view>
</template>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
<template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>
模板的作用域
模板拥有自己的作用域,只能使用 data 传入的数据以及模板定义文件中定义的 <wxs /> 模块。
引用
WXML 提供两种文件引用方式import和include。
import
import可以在该文件中使用目标文件定义的template,如:
在 item.wxml 中定义了一个叫item的template:
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
在 index.wxml 中引用了 item.wxml,就可以使用item模板:
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
import 的作用域
import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template。
如:C import B,B import A,在 C 中可以使用 B 定义的template,在 B 中可以使用 A 定义的template,但是 C 不能使用 A 定义的template。
<!-- A.wxml -->
<template name="A">
<text> A template </text>
</template>
<!-- B.wxml -->
<import src="a.wxml"/>
<template name="B">
<text> B template </text>
</template>
<!-- C.wxml -->
<import src="b.wxml"/>
<template is="A"/> <!-- Error! Can not use tempalte when not import A. -->
<template is="B"/>
include
include 可以将目标文件除了 <template/> <wxs/> 外的整个代码引入,相当于是拷贝到 include 位置,如:
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>
事件
什么是事件
- 事件是视图层到逻辑层的通讯方式。
- 事件可以将用户的行为反馈到逻辑层进行处理。
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
- 事件对象可以携带额外信息,如 id, dataset, touches。
事件的使用方式
- 在组件中绑定一个事件处理函数。
如bindtap,当用户点击该组件的时候会在该页面对应的 Page 中找到相应的事件处理函数。
<view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view>
- 在相应的 Page 定义中写上相应的事件处理函数,参数是event。
Page({
tapName: function(event) {
console.log(event)
}
})
事件分类
事件分为冒泡事件和非冒泡事件:
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML的冒泡事件列表:
| 类型 | 触发条件 | 绑定方式 |
|---|---|---|
| touchstart | 手指触摸动作开始 | |
| touchmove | 手指触摸后移动 | |
| touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | |
| touchend | 手指触摸动作结束 | |
| tap | 手指触摸后马上离开 | bindtap或bind:tap |
| longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 | 1.5.0 |
| longtap | 手指触摸后,超过350ms再离开(推荐使用 longpress 事件代替) | |
| transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 | |
| animationstart | 会在一个 WXSS animation 动画开始时触发 | |
| animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | |
| animationend | 会在一个 WXSS animation 动画完成时触发 | |
| touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 | 1.9.90 |
注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的submit事件,input 的input事件,scroll-view 的scroll事件,(详见各个组件)
| 类型 | 绑定方式 | 事件描述 |
|---|---|---|
| tap | bindtap或bind:tap | |
| input | bindinput或bind:input | |
| change | bindchange或bind:change |
普通事件绑定
事件绑定的写法类似于组件的属性,如:
<view bindtap="handleTap">
Click here!
</view>
如果用户点击这个 view ,则页面的 handleTap 会被调用。
事件绑定函数可以是一个数据绑定,如:
<view bindtap="{{ handlerName }}">
Click here!
</view>
此时,页面的 this.data.handlerName 必须是一个字符串,指定事件处理函数名;如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)。
自基础库版本 1.5.0 起,在大多数组件和自定义组件中, bind 后可以紧跟一个冒号,其含义不变,如 bind:tap 。基础库版本 2.8.1 起,在所有组件中开始提供这个支持。
绑定并阻止事件冒泡
除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。
例如在下边这个例子中,点击 inner view 会先后调用handleTap3和handleTap2(因为 tap 事件会冒泡到 middle view,而 middle view 阻止了 tap 事件冒泡,不再向父节点传递),点击 middle view 会触发handleTap2,点击 outer view 会触发handleTap1。
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
互斥事件绑定
自基础库版本 2.8.2 起,除 bind 和 catch 外,还可以使用 mut-bind 来绑定事件。一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。
换而言之,所有 mut-bind 是“互斥”的,只会有其中一个绑定函数被触发。同时,它完全不影响 bind 和 catch 的绑定效果。
例如在下边这个例子中,点击 inner view 会先后调用 handleTap3 和 handleTap2 ,点击 middle view 会调用 handleTap2 和 handleTap1 。
<view id="outer" mut-bind:tap="handleTap1">
outer view
<view id="middle" bindtap="handleTap2">
middle view
<view id="inner" mut-bind:tap="handleTap3">
inner view
</view>
</view>
</view>
事件的捕获阶段
自基础库版本 1.5.0 起,触摸类事件支持捕获阶段。捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。
在下面的代码中,点击 inner view 会先后调用handleTap2、handleTap4、handleTap3、handleTap1。
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2。
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
事件对象
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
BaseEvent 基础事件对象属性列表:
| 属性 | 类型 | 说明 | 基础库版本 |
|---|---|---|---|
| type | String | 事件类型 | |
| timeStamp | Integer | 事件生成时的时间戳 | |
| target | Object | 触发事件的组件的一些属性值集合 | |
| currentTarget | Object | 当前组件的一些属性值集合 | |
| mark | Object | 事件标记数据 | 2.7.1 |
CustomEvent 自定义事件对象属性列表(继承 BaseEvent):
| 属性 | 类型 | 说明 |
|---|---|---|
| detail | Object | 额外的信息 |
TouchEvent 触摸事件对象属性列表(继承 BaseEvent):
| 属性 | 类型 | 说明 |
|---|---|---|
| touches | Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 |
| changedTouches | Array | 触摸事件,当前变化的触摸点信息的数组 |
特殊事件: canvas 中的触摸事件不可冒泡,所以没有 currentTarget。
type
代表事件的类型。
timeStamp
页面打开到触发事件所经过的毫秒数。
target
触发事件的源组件。
| 属性 | 类型 | 说明 |
|---|---|---|
| id | String | 事件源组件的id |
| dataset | Object | 事件源组件上由data-开头的自定义属性组成的集合 |
currentTarget
事件绑定的当前组件。
| 属性 | 类型 | 说明 |
|---|---|---|
| id | String | 当前组件的id |
| dataset | Object | 当前组件上由data-开头的自定义属性组成的集合 |
说明: target 和 currentTarget 可以参考上例中,点击 inner view 时,handleTap3 收到的事件对象 target 和 currentTarget 都是 inner,而 handleTap2 收到的事件对象 target 就是 inner,currentTarget 就是 middle。
dataset
在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
在 WXML 中,这些自定义数据以 data- 开头,多个单词由连字符 - 连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。如:
data-element-type,最终会呈现为event.currentTarget.dataset.elementType;data-elementType,最终会呈现为event.currentTarget.dataset.elementtype。
示例:
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({
bindViewTap:function(event){
event.currentTarget.dataset.alphaBeta === 1 // - 会转为驼峰写法
event.currentTarget.dataset.alphabeta === 2 // 大写会转为小写
}
})
mark
在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点。此外, mark 还可以用于承载一些自定义数据(类似于 dataset )。
当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark 。)
代码示例:
<view mark:myMark="last" bindtap="bindViewTap">
<button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button>
</view>
在上述 WXML 中,如果按钮被点击,将触发 bindViewTap 和 bindButtonTap 两个事件,事件携带的 event.mark 将包含 myMark 和 anotherMark 两项。
Page({
bindViewTap: function(e) {
e.mark.myMark === "last" // true
e.mark.anotherMark === "leaf" // true
}
})
mark 和 dataset 很相似,主要区别在于: mark 会包含从触发事件的节点到根节点上所有的 mark: 属性值;而 dataset 仅包含一个节点的 data- 属性值。
细节注意事项:
- 如果存在同名的
mark,父节点的mark会被子节点覆盖。 - 在自定义组件中接收事件时,
mark不包含自定义组件外的节点的mark。 - 不同于
dataset,节点的mark不会做连字符和大小写转换。
touches
touches 是一个数组,每个元素为一个 Touch 对象(canvas 触摸事件中携带的 touches 是 CanvasTouch 数组)。 表示当前停留在屏幕上的触摸点。
Touch 对象
| 属性 | 类型 | 说明 |
|---|---|---|
| identifier | Number | 触摸点的标识符 |
| pageX, pageY | Number | 距离文档左上角的距离,文档的左上角为原点 ,横向为 X 轴,纵向为 Y 轴 |
| clientX, clientY | Number | 距离页面可显示区域(屏幕除去导航条)左上角距离,横向为 X 轴,纵向为 Y 轴 |
CanvasTouch 对象
| 属性 | 类型 | 说明 | 特殊说明 |
|---|---|---|---|
| identifier | Number | 触摸点的标识符 | |
| x, y | Number | 距离 Canvas 左上角的距离,Canvas 的左上角为原点 ,横向为 X 轴,纵向为 Y 轴 |
changedTouches
changedTouches 数据格式同 touches。 表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。
detail
自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。
点击事件的detail 带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。
事件绑定修改数据
bindtap="handleclick"
handleclick(){
this.setData({
count: this.data.count + 1
})
}
事件传参
小程序中的事件传参比较特殊,不能在绑定事件的同时为事件处理函数传递参数,因为小程序会把bindtap的属性值统一当作事件名称来处理。例如:下面代码将会被认为调用了一个名称为toLink(item.link)的函数,
bindtap="toLink(item.link)"
可以为组件提供data-*自定义属性传参,其中*代表的是参数的名字,示例如下
bindtap="toLink"
data-link="{{item.link}}"
toLink(e) {
console.log("点击了图标,要跳转页面", e.currentTarget.dataset.link)
},
bindinput的语法格式
实现文本框和data之间的数据同步
<input
placeholder="请输入"
auto-focus
bind:input="handleinput"
value="{{msg}}"
handleinput(e) {
this.setData({
msg: e.detail.value
})
},
网络数据请求
跨域问题只存在于基于浏览器的Web开发中,小程序的宿主环境不是浏览器而是微信客户端,所以小程序中没有跨域问题
wx.request({
url: 'example.php', //请求地址
method:"get",//请求参数
data: { //请求参数
x: '',
y: ''
},
header: { //设置请求头
'content-type': 'application/json' // 默认值
},
dataType:"json", //请求返回结果的数据类型
success (res) {//请求成功的回调
console.log(res.data)
},
fail: function(res) {},// 请求失败执行的回调函数
complete: function(res) {},// 接口调用结束的回调函数(调用成功、失败都会执行)
})
页面导航
1.声明式导航
在页面上生命一个
navigator导航组件通过点击
navigator组件实现页面跳转url表示要跳转的页面的地址,必须以/开头;open-type表示跳转的方式,如tabbar页面必须用switchTab,默认为navigate
open-type 说明 备注 navigate 对应 wx.navigateTo 或 wx.navigateToMiniProgram 的功能 打开新页面,新页面会压栈 redirect 对应 wx.redirectTo 的功能 页面重定向,新页面不会压栈,而是把原来的页面替换掉 switchTab 对应 wx.switchTab 的功能 跳转tabBar页面,并关闭其他所有非 tabBar 页面 reLaunch 对应 wx.reLaunch 的功能 重新加载指定页面,页面全部出栈,只留下新的页面 navigateBack 对应 wx.navigateBack 或 wx.navigateBackMiniProgram (基础库 2.24.4 版本支持)的功能 回到上一个页面,通过delta指定回退的层数,默认值为1,当delta超出历史记录,返回首页 exit 退出小程序, target="miniProgram"时生效
<!--hover-class="none" 跳转到Tabbar页面--> <navigator url="/pages/index/index" open-type="switchTab" >跳转index</navigator> <!--hover-class="none"后退导航--> <navigator url="/pages/index/index" open-type="navigateBack" delta="1">跳转index</navigator> <view> <!--hover-class="none" 导航传参--> <navigator url="/pages/demo2/demo2?name=zhangsan&age=12" hover-class="none">跳转demo2</navigator> </view> //2.demo2.js //在onload方法中通过options来获取页面1传递的参数 onLoad: function (options) { console.log(options) },
2.编程式导航
wx.navigateTo({
url: '/pagex/test/test?id=1',//导航传参
})
接收参数
onLoad(options){
console.log(options)//options就是导航传递过来的参数
}
导航模式
调用小程序的导航API实现页面的跳转
<view>
<navigator url="/pages/demo2/demo2?name=zhangsan&age=12" open-type="redirectTo" >跳转demo2</navigator>
</view>
navigate 打开新页面,新页面会压栈
redirect 页面重定向,新页面不会压栈,而是把原来的页面替换掉
navigateBack 回到上一个页面
reLaunch 重新加载指定页面,页面全部出栈,只留下新的页面
switchTab 跳转tabBar页面,并关闭其他所有非 tabBar 页面
// https://segmentfault.com/a/1190000014996318?utm_source=index-hottest
//注意点:
navigate, redirect 只能打开非 tabBar 页面。
switchTab 只能打开 tabBar 页面。
reLaunch 可以打开任意页面。
//回退两层,可以通过getCurrentPages()方法来查看栈中的页面个数
wx.navigateBack({
//当delta超出历史记录,返回首页
delta:2
})
页面事件
1.下拉刷新:移动端中手指在屏幕下滑从而重新加载页面数据
全局开启下拉刷新:在app.json的window节点中,将enablePullDownRefresh设置为true
局部开启下拉刷新(推荐):在页面的json文件中将enablePullDownRefresh设置为true
{
"usingComponents": {},
"enablePullDownRefresh":true,//开启下拉刷新
"backgroundColor":"#D8BFD8",//窗口的背景色
"backgroundTextStyle":"light"//loading的样式颜色
}
在js中的onPullDownRefresh中可以监听到下拉刷新的操作,在这里进行数据的处理,但是下拉刷新后刷新的loading会一直显示,需要调用 wx.stopPullDownRefresh()停止下拉刷新的操作
2.上拉触底:移动端中手指在屏幕上滑从而重新加载更多数据,可以通过onReachBottomDistance来配置上拉触底触发事件的距离,默认为50px
在js中的onReachBottom中可以监听到上拉触底事件,这里应该进行节流的处理,上一个请求未处理完毕,不允许进行下一次请求
生命周期
应用生命周期:小程序从启动=>运行=>销毁的过程
页面生命周期:每个页面加载=>渲染=>销毁的过程
页面的生命周期范围较小,应用生命周期的范围较大,可以在特定的时间点执行某些特定的操作
应用生命周期
onLaunch:function(){}//小程序初始化完成,会触发onLaunch(全局只触发一次)
onShow:function(){}//小程序启动,或从后台进入前台显示,会触发onShow
onHide:function(){}//小程序从前台进入后台,会触发onHide
页面生命周期
onLoad:function(options){}//监听页面加载,一个页面只调用一次
onShow:function(){}//监听页面显示
onReady:function(){}//监听页面初次渲染完成,一个页面只调用一次
onHide:function(){}//监听页面隐藏
onUnLoad:function(){}//监听页面卸载,一个页面只调用一次
