1.基础
1.准备工作
- 1.注册公众平台账号(免费)
- 2.下载小程序开发工具(免费)
2.开发步骤
1.第一个小程序
- 1.小程序的
AppID
相当于小程序平台的一个身份证,这里使用的是测试号(注意区别于服务号或订阅号的AppID
)
- 2.测试号默认不使用云服务,根据需求选择模板,这里选择
JS-基础模板
2.小程序代码构成
1.JSON配置
- 1.
JSON
是一种数据格式,小程序中的JSON
用于静态配置
- 2.配置文件说明(根目录)
- 1.
app.json
:当前小程序的全局配置,具体可参考小程序全局配置说明
- 1.
页面路径
- 1.
pages
:用于描述当前小程序所有页面路径,用于让微信客户端清楚当前小程序页面定义的目录
- 2.
界面表现
- 2.
window
:用于定义小程序所有页面的顶部背景颜色,文字颜色定义等
- 3.
网络超时时间
- 4.
底部 tab
- 2.
project.config.json
:当前小程序开发工具的个性化配置,具体可参考小程序个性化配置说明
- 3.
page.json
:当前小程序独立页面的属性配置,具体可参考小程序独立页面配置说明
2.WXML + WXSS + JS
- 1.网页编程采用的是
HTML + CSS + JS
组合
- 2.小程序开发采用
WXML + WXSS + JS
组合
- 3.
WXML
类似于HTML
,具体可参考WXML语法参考;WXSS
类似于CSS
- 4.
MVVM开发模式(React,Vue)
:把渲染和逻辑分离
- 1.网页开发流程中,通过
JS
操作DOM
(对应 HTML
的描述产生的树),以引起界面的一些变化响应用户的行为
- 2.用户点击某个按钮的时候,
JS
会记录一些状态到JS
变量里,同时通过DOM API
操 DOM
的属性或者行为,进而引起界面一些变化
- 3.当项目越来越大,代码会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式
- 4.不要让
JS
直接操控DOM
,JS
只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可,小程序的框架也是用到了这个思路
3.小程序运行环境
- 1.小程序的运行环境分为
- 2.小程序的渲染层和逻辑层分别由2个线程管理
- 1.渲染层的界面使用
WebView
进行渲染
- 2.逻辑层采用
JsCore
线程运行JS
脚本
- 3.一个小程序存在多个界面,所以渲染层存在多个
WebView
线程,这两个线程的通信会经由微信客户端Native
做中转,逻辑层发送网络请求也经由微信客户端
转发
4.框架
1.列表渲染
<view wx:for="{{array}}"> {{item}} view>
Page({
data: {
array: [1, 2, 3, 4, 5]
}
})
2.条件渲染
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW view>
<view wx:elif="{{view == 'APP'}}"> APP view>
<view wx:else="{{view == 'MINA'}}"> MINA view>
Page({
data: {
view: 'MINA'
}
})
3.模板
<template name="staffName">
<view>
FirstName: {{firstName}}, LastName: {{lastName}}
view>
template>
<template is="staffName" data="{{...staffA}}">template>
<template is="staffName" data="{{...staffB}}">template>
<template is="staffName" data="{{...staffC}}">template>
Page({
data: {
staffA: {firstName: 'Hulk', lastName: 'Hu'},
staffB: {firstName: 'Shang', lastName: 'You'},
staffC: {firstName: 'Gideon', lastName: 'Lin'}
}
})
4.引用
- 网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应
- 小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作
- 程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的
- 网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具,小程序中三大运行环境也是有所区别的
- 网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序账号、安装小程序开发者工具、配置项目等等过程方可完成
开始
- 1.开发小程序的第一步,你需要拥有一个小程序账号,通过这个账号你就可以管理你的小程序
- 2.申请账号
- 3.进入小程序注册页 根据指引填写信息和提交相应的资料,就可以拥有自己的小程序账号。
小程序注册
- 4.登录 小程序后台 ,我们可以在菜单 “开发”-“开发设置” 看到小程序的 AppID 了
小程序的基本文件结构
- 1.一个小程序项目必须有这
3
个描述App
的文件,这3个文件是应用程序级别的文件
- 1.app.js
- 2.app.json
- 3.app.wxss
- 2.它们必须放在应用程序的根目录下,否则小程序会提示找不到
app.json
文件
- 3.接着是和这3个应用程序级别文件平行的pages文件夹
- 4.一个小程序由若干个页面文件构成,比如图3-1中pages文件夹下就有2个页面,分别是index页面和logs页面。每个页面可以由4个文件构成,分别是:
- .js
- .wxml
- .wxss
- .json文件
- 1.wxml文件类似于我们熟悉的HTML文件,用来编写页面的标签和骨架,不同的是wxml文件里的标签元素不可以使用HTML标签,只能使用小程序自己封装的一些组件,这些组件也是我们后面要重点学习的知识。>* 2.wxss文件的作用类似于我们熟悉的CSS文件,用于编写小程序的样式,实际上小程序的样式编写语言就是CSS,只是把.css文件换成了.wxss文件。
- 3.json文件用来配置页面的样式与行为。
- 4.js文件类似于我们前端编程中的JavaScript文件,用来编写小程序的页面逻辑
- 5.以上4种类型的页面文件的文件名称必须相同,这是要注意的一个地方
- 小程序的4种页面级别文件同3个应用程序级别文件相比,多出了一个wxml页面标签文件,其他3个的作用基本相似,只不过页面文件作用于页面本身而应用程序文件作用于应用程序整体
- 除了pages文件夹外,官方的示例项目中还有一个utils文件夹,这个文件夹用来存放一些公共的js文件,比如utils下面的util.js。我们可以任意定义类似于utils文件夹的目录,并放在小程序的任意位置,小程序对此并没有任何限制
从零开始编写一个小程序页面
- 新建一个项目,并且不勾选【在当前目录中创建quick start项目】这个选项,因为我们要从零开始编写一个项目,所以每个文件都将由自己亲手创建。项目创建后,会出现一个如图3-2所示的错误提示,这是因为现在的项目里还没有任何文件,由于缺少必要的文件,所以小程序会报错。之所以完全新建一个全新的项目,是为了向开发者展示这些常见的错误消息,如果大家不想经历这些错误,那么可以在官方提供的quick start项目上修改
小程序配置
1.全局配置
- 1.小程序根目录下的
app.json
文件用来对微信小程序进行全局配置,文件内容为一个JSON
对象
属性 |
类型 |
必填 |
描述 |
最低版本 |
entryPagePath |
string |
否 |
小程序默认启动首页 |
|
pages |
string[] |
是 |
页面路径列表 |
|
window |
Object |
否 |
全局的默认窗口表现 |
|
sitemapLocation |
string |
是 |
指明 sitemap.json 的位置 |
|
tabBar |
Object |
否 |
底部 tab 栏的表现 |
|
debug |
boolean |
否 |
是否开启 debug 模式,默认关闭 |
|
workers |
string |
否 |
Worker代码放置的目录 |
1.9.90 |
plugins |
Object |
否 |
使用到的插件 |
1.9.6 |
networkTimeout |
Object |
否 |
网络超时时间 |
|
functionalPages |
boolean |
否 |
是否启用插件功能页,默认关闭 |
2.1.0 |
subpackages |
Object[] |
否 |
分包结构配置 |
1.7.3 |
requiredBackgroundModes |
string[] |
否 |
需要在后台使用的能力,如「音乐播放」 |
|
requiredPrivateInfos |
string[] |
否 |
调用的地理位置相关隐私接口 |
|
preloadRule |
Object |
否 |
分包预下载规则 |
2.3.0 |
resizable |
boolean |
否 |
PC小程序是否支持用户任意改变窗口大小(包括最大化窗口);iPad 小程序是否支持屏幕旋转。默认关闭 |
2.3.0 |
usingComponents |
Object |
否 |
全局自定义组件配置 |
开发者工具 1.02.1810190 |
permission |
Object |
否 |
小程序接口权限相关设置 |
微信客户端 7.0.0 |
style |
string |
否 |
指定使用升级后的weui样式 |
2.8.0 |
useExtendedLib |
Object |
否 |
指定需要引用的扩展库 |
2.2.1 |
entranceDeclare |
Object |
否 |
微信消息用小程序打开 |
微信客户端 7.0.9 |
darkmode |
boolean |
否 |
小程序支持 DarkMode |
2.11.0 |
themeLocation |
string |
否 |
指明 theme.json 的位置,darkmode为true为必填 |
开发者工具 1.03.2004271 |
lazyCodeLoading |
string |
否 |
配置自定义组件代码按需注入 |
2.11.1 |
singlePage |
Object |
否 |
单页模式相关配置 |
2.12.0 |
supportedMaterials |
Object |
否 |
聊天素材小程序打开相关配置 |
2.14.3 |
serviceProviderTicket |
string |
否 |
定制化型服务商票据 |
|
embeddedAppIdList |
string[] |
否 |
半屏小程序 appId |
2.20.1 |
halfPage |
Object |
否 |
视频号直播半屏场景设置 |
2.18.0 |
debugOptions |
Object |
否 |
调试相关配置 |
2.22.1 |
enablePassiveEvent |
Object或boolean |
否 |
touch 事件监听是否为 passive |
2.24.1 |
resolveAlias |
Object |
否 |
自定义模块映射规则 |
|
renderer |
string |
否 |
全局默认的渲染后端 |
2.30.4 |
rendererOptions |
Object |
否 |
渲染后端选项 |
2.31.1 |
componentFramework |
string |
否 |
组件框架,详见相关文档 |
2.30.4 |
miniApp |
Object |
否 |
多端模式场景接入身份管理服务时开启小程序授权页相关配置,详见相关文档 |
|
static |
Object |
否 |
正常情况下默认所有资源文件都被打包发布到所有平台,可以通过 static 字段配置特定每个目录/文件只能发布到特定的平台(多端场景) 相关文档 |
|
1.entryPagePath
- 1.指定小程序的默认启动路径(首页)
- 2.常见情景是从微信聊天列表页下拉启动、小程序列表启动等
- 3.如果不填将默认为
pages
列表的第一项
- 4.不支持带页面路径参数
2.pages
{
"pages": ["pages/index/index", "pages/logs/logs"]
}
- 1.指定小程序由哪些页面组成,每一项都对应一个页面的路径(含文件名) 信息
- 2.文件名不需要写文件后缀,框架会自动去寻找对应位置的
.json, .js, .wxml, .wxss
四个文件进行处理
- 3.未指定
entryPagePath
时,数组的第一项代表小程序的初始页面(首页)
- 4.小程序中新增/减少页面,都需要对
pages
数组进行修改
3.window
属性 |
类型 |
默认值 |
描述 |
最低版本 |
navigationBarBackgroundColor |
HexColor |
#000000 |
导航栏背景颜色 |
|
navigationBarTextStyle |
string |
white |
导航栏标题、状态栏颜色,仅支持 black/white |
|
navigationBarTitleText |
string |
|
导航栏标题文字内容 |
|
navigationStyle |
string |
default |
导航栏样式,仅支持以下值:default 默认样式;custom 自定义导航栏,只保留右上角胶囊按钮 |
iOS/Android 微信客户端 6.6.0,Windows 微信客户端不支持 |
homeButton |
boolean |
default |
在非首页、非页面栈最底层页面或非tabbar内页面中的导航栏展示home键 |
微信客户端 8.0.24 |
backgroundColor |
HexColor |
#ffffff |
窗口的背景色 |
|
backgroundTextStyle |
string |
dark |
下拉 loading 的样式,仅支持dark/light |
|
backgroundColorTop |
string |
#ffffff |
顶部窗口的背景色,仅 iOS 支持 |
微信客户端 6.5.16 |
backgroundColorBottom |
string |
#ffffff |
底部窗口的背景色,仅 iOS 支持 |
微信客户端 6.5.16 |
enablePullDownRefresh |
boolean |
false |
是否开启全局的下拉刷新。详见 Page.onPullDownRefresh |
|
onReachBottomDistance |
number |
50 |
页面上拉触底事件触发时距页面底部距离,单位为 px。详见 Page.onReachBottom |
|
pageOrientation |
string |
portrait |
屏幕旋转设置,支持 auto / portrait / landscape详见 响应显示区域变化 |
2.4.0 (auto) / 2.5.0 (landscape) |
restartStrategy |
string |
homePage |
重新启动策略配置 |
2.8.0 |
initialRenderingCache |
|
string |
页面初始渲染缓存配置,支持 static / dynamic |
2.11.1 |
visualEffectInBackground |
string |
none |
切入系统后台时,隐藏页面内容,保护用户隐私。支持 hidden / none |
2.15.0 |
handleWebviewPreload |
string |
static |
控制预加载下个页面的时机。支持 static / manual / auto |
2.15.0 |
- 1.注意
- 1.
HexColor
(十六进制颜色值,如#ff00ff
)
- 2.用于设置小程序的状态栏、导航条、标题、窗口背景色
- 3.关于
navigationStyle
- 1.
iOS/Android
客户端7.0.0
以下版本,navigationStyle
只在app.json
中生效。
- 2.
iOS/Android
客户端6.7.2
版本开始,navigationStyle: custom
对web-view
组件无效
- 3.开启
custom
后,低版本客户端需做好兼容,开发者工具基础库版本切到1.7.0
(不代表最低版本,只供调试用)可方便切到旧视觉
- 4.
Windows
客户端3.0
及以上版本,为了给用户提供更符合桌面软件的使用体验,统一了小程序窗口的导航栏,navigationStyle: custom
不再生效
- 2.实例
{
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能演示",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
}
1.restartStrategy
可选值 |
含义 |
homePage |
(默认值)如果从这个页面退出小程序,下次将从首页冷启动 |
homePageAndLatestPage |
如果从这个页面退出小程序,下次冷启动后立刻加载这个页面,页面的参数保持不变(不可用于 tab 页) |
tabBar
- 如果小程序是一个多
tab
应用(客户端窗口的底部或顶部有 tab
栏可以切换页面),可以通过 tabBar
配置项指定 tab
栏的表现,以及 tab
切换时显示的对应页面
tabBar
如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 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 tabBar 的位置,仅支持 bottom / top |
|
|
|
|
custom |
boolean 否 false 自定义 tabBar,见详情 2.5.0 |
|
|
|
|
2.页面配置
- 1.每一个小程序页面可使用同名
.json
文件对本页面的窗口表现进行配置,页面中配置项会覆盖根目录的app.json
的window
中相同的配置项
小程序运行机制
1.小程序的生命周期
- 1.小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现
2.小程序启动
- 1.从用户认知的角度看,广义的小程序启动可以分为两种情况,一种是冷启动,一种是热启动
- 1.冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动
- 2.热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动
- 2.从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台
3.前台与后台
- 1.小程序启动后,界面被展示给用户,此时小程序处于「前台」状态。
- 2.当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了「后台」状态,此时小程序还可以短暂运行一小段时间,但部分 API 的使用会受到限制。切后台的方式包括但不限于以下几种:
- 1.点击右上角胶囊按钮离开小程序
- 2.iOS 从屏幕左侧右滑离开小程序
- 3.安卓点击返回键离开小程序
- 4.小程序前台运行时直接把微信切后台(手势或 Home 键)
- 5.小程序前台运行时直接锁屏
- 3.当用户再次进入微信并打开小程序,小程序又会重新进入「前台」状态
4.挂起
- 1.小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止小程序 JS 线程的执行,小程序进入「挂起」状态。此时小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在小程序再次进入「前台」时触发。
- 2.当开发者使用了后台音乐播放、后台地理位置等能力时,小程序可以在「后台」持续运行,不会进入到「挂起」状态
5.小程序销毁
- 1.如果用户很久没有使用小程序,或者系统资源紧张,小程序会被「销毁」,即完全终止运行。具体而言包括以下几种情形:
- 1.当小程序进入后台并被「挂起」后,如果很长时间(目前是 30 分钟)都未再次进入前台,小程序会被销毁。
- 2.当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
- 1.在 iOS 上,当微信客户端在一定时间间隔内连续收到系统内存告警时,会根据一定的策略,主动销毁小程序,并提示用户 「运行内存不足,请重新打开该小程序」。具体策略会持续进行调整优化。
- 2.建议小程序在必要时使用 wx.onMemoryWarning 监听内存告警事件,进行必要的内存清理
2.基础库 1.1.0 及以上,1.4.0 以下版本: 当用户从扫一扫、转发等入口(场景值为1007, 1008, 1011, 1025)进入小程序,且没有置顶小程序的情况下退出,小程序会被销毁。
6. 小程序冷启动的页面
- 小程序冷启动时,打开的页面有以下情况
- 1.(A 类场景)若启动的场景中不带 path
基础库 2.8.0 以下版本,进入首页
基础库 2.8.0 及以上版本遵循「重新启动策略」,可能是首页或上次退出的页面
- 2.(B 类场景)若启动的场景中带有 path,则启动进入对应 path 的页面
7.重新启动策略
{
"restartStrategy": "homePage"
}
可选值 |
含义 |
homePage |
(默认值)如果从这个页面退出小程序,下次将从首页冷启动 |
homePageAndLatestPage |
如果从这个页面退出小程序,下次冷启动后立刻加载这个页面,页面的参数保持不变(不可用于 tab 页) |
- 1.小程序冷启动时,如果启动时不带 path(A 类场景),默认情况下将会进入小程序的首页。 在页面对应的 json 文件中(也可以全局配置在 app.json 的 window 段中),指定 restartStrategy 配置项可以改变这个默认的行为,使得从某个页面退出后,下次 A 类场景的冷启动可以回到这个页面
- 2.注意:即使不配置为 homePage ,小程序如果退出过久(当前默认一天时间,可以使用退出状态来调整),下次冷启动时也将不再遵循 restartStrategy 的配置,而是直接从首页冷启动。
- 3.无论如何,页面中的状态并不会被保留,如输入框中的文本内容、 checkbox 的勾选状态等都不会还原。如果需要还原或部分还原,需要利用退出状态
8. 小程序热启动的页面
- 1.小程序热启动时,打开的页面有以下情况
- (A 类场景)若启动的场景中不带 path,则保留上次的浏览的状态
- (B 类场景)若启动的场景中带有 path,则 reLaunch 到对应 path 的页面
- A 类场景常见的有下列场景值
场景值ID |
说明 |
1001 发现栏小程序主入口,「最近使用」列表(基础库2.2.4版本起包含「我的小程序」列表) |
|
1003 |
星标小程序列表 |
1023 |
系统桌面小图标打开小程序 |
1038 |
从其他小程序返回小程序 |
1056 |
聊天顶部音乐播放器右上角菜单,打开小程序 |
1080 |
客服会话菜单小程序入口,打开小程序 |
1083 |
公众号会话菜单小程序入口 ,打开小程序(只有腾讯客服小程序有) |
1089 |
聊天主界面下拉,打开小程序/微信聊天主界面下拉,「最近使用」栏(基础库2.2.4版本起包含「我的小程序」栏) |
1090 |
长按小程序右上角菜单,打开小程序 |
1103 |
发现-小程序主入口我的小程序,打开小程序 |
1104 |
聊天主界面下拉,从我的小程序,打开小程序 |
1113 |
安卓手机负一屏,打开小程序 |
1114 |
安卓手机侧边栏,打开小程序 |
1117 |
后台运行小程序的管理页中,打开小程序 |
9.退出状态
{
"restartStrategy": "homePageAndLatestPage"
}
Page({
onLoad: function() {
var prevExitState = this.exitState
if (prevExitState !== undefined) {
prevExitState.myDataField === 'myData'
}
},
onSaveExitState: function() {
var exitState = { myDataField: 'myData' }
return {
data: exitState,
expireTimeStamp: Date.now() + 24 * 60 * 60 * 1000
}
}
})
onSaveExitState 返回值可以包含两项:
- 1.基础库 2.7.4 开始支持,低版本需做兼容处理。
- 2.每当小程序可能被销毁之前,页面回调函数 onSaveExitState 会被调用。如果想保留页面中的状态,可以在这个回调函数中“保存”一些数据,下次启动时可以通过 exitState 获得这些已保存数据
字段名 类型 含义
data Any 需要保存的数据(只能是 JSON 兼容的数据)
expireTimeStamp Number 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天)
WXML
- 1.
WXML(WeiXin Markup Language)
:微信小程序框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构
1.数据绑定
- 1.数据绑定使用
Mustache
语法(双大括号)将变量包起来,为了使用户界面与业务数据(内容)分离
- 2.
WXML
中的动态数据均来自对应 Page
的 data
- 3.注意
- 1.如果
双大括号
是作用于标签内部(属性)
则需要带上双引号
- 2.如果是作用于
两个标签内部
则不需要带上双引号
- 4.数据绑定可以作用于以下几种类型
1.内容
<view> {{message}} view>
Page({
data: {
message: 'Hello MINA!'
}
})
2.组件属性
<view id="item-{{id}}"> view>
Page({
data: {
id: 0
}
})
3.控制属性
<view wx:if="{{condition}}"> view>
Page({
data: {
condition: true
}
})
4.关键字
5.运算
- 1.在
{{}}
可以内进行简单的运算,支持的有如下几种方式
1.三元运算
<view hidden="{{flag ? true : false}}"> Hidden view>
2.算数运算
<view> {{a + b}} + {{c}} + d view>
Page({
data: {
a: 1,
b: 2,
c: 3
}
})
6.逻辑判断
<view wx:if="{{length > 5}}"> view>
7.字符串运算
<view>{{"hello" + name}}view>
Page({
data:{
name: 'MINA'
}
})
8.数据路径运算
<view>{{object.key}} {{array[0]}}view>
Page({
data: {
object: {
key: 'Hello '
},
array: ['MINA']
}
})
9.组合
- 1.可以在
Mustache
(双大括号)内直接进行组合,构成新的对象或数组
1.数组
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} view>
Page({
data: {
zero: 0
}
})
2.对象
<template is="objectCombine" data="{{for: a, bar: b}}">template>
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}">template>
<template is="objectCombine" data="{{foo, bar}}">template>
Page({
data: {
a: 1,
b: 2
}
})
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
c: 3,
d: 4
}
}
})
Page({
data: {
foo: 'my-foo',
bar: 'my-bar'
}
})
2.列表渲染
1.wx:for
<view wx:for="{{array}}">
{{index}}: {{item.message}}
view>
<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>
view>
view>
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
- 1.组件上使用
wx:for
控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件,同时wx:for
也可以嵌套
- 2.默认数组的当前项的下标变量名默认为
index
,数组当前项的变量名默认为 item
- 3.使用
wx:for-item
可以指定数组当前元素的变量名
- 4.使用
wx:for-index
可以指定数组当前下标的变量名
2.block wx:for
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: view>
<view> {{item}} view>
block>
- 1.
wx:for
可以用在
标签上,以渲染一个包含多节点的结构块
3.wx:key
<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: 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
})
}
})
- 1.如果列表中项目的位置会动态改变或有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如
input
中的输入内容,switch
的选中状态),需要使用wx:key
指定列表中项目的唯一的标识符
- 2.当数据改变触发渲染层重新渲染的时候,会校正带有
key
的组件,框架会确保它们被重新排序而不是重新创建,以确保使组件保持自身的状态并且提高列表渲染时的效率
- 3.
wx:key
的值以两种形式提供
- 1.
字符串
:代表在for
循环的array
中item
的某个 property
,该property
的值需要是列表中唯一的字符串或数字,且不能动态改变
- 2.保留关键字
*this
:代表在for
循环中的item
本身,这种表示需要item
本身是一个唯一的字符串或数字
- 4.如不提供
wx:key
会报一个warning
, 如果明确知道该列表是静态或不必关注其顺序则可以选择忽略
4.注意
- 1.花括号和引号之间如果有
空格
,将最终被解析成为字符串<view wx:for="{{[1,2,3]}} ">
{{item}}
view>
<view wx:for="{{[1,2,3] + ' '}}">
{{item}}
view>
- 2.当
wx:for
的值为字符串时,会将字符串解析成字符串数组<view wx:for="array">
{{item}}
view>
<view wx:for="{{['a','r','r','a','y']}}">
{{item}}
view>
3.条件渲染
1.wx:if
<view wx:if="{{condition}}"> True view>
<view wx:if="{{length > 5}}"> 1 view>
<view wx:elif="{{length > 2}}"> 2 view>
<view wx:else> 3 view>
- 1.使用
wx:if=""
来判断是否需要渲染该代码块
2.block wx:if
<block wx:if="{{true}}">
<view> view1 view>
<view> view2 view>
block>
- 1.因为
wx:if
是一个控制属性,需要将它添加到一个标签上
- 2.如果要一次性判断多个组件标签,可以使用一个
标签将多个组件包装起来,并在上边使用wx:if
控制属性
- 3.注意:
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性
3.wx:if 区别 hidden
- 1.
wx:if
- 1.因为
wx:if
之中的模板可能包含数据绑定,所以当wx:if
的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染
- 2.同时
wx:if
是惰性的,如果初始渲染条件为false
,框架什么也不做,在条件第一次为真时才开始局部渲染
- 2.
hidden
- 1.
hidden
组件始终会被渲染,只是简单的控制显示与隐
- 3.总结:
- 1.
wx:if
有更高的切换消耗
- 2.
hidden
有更高的初始渲染消耗
- 3.因此如果需要
频繁切换
的情景下用 hidden
更好,如果运行时条件不大可能改变则wx:if
较好
4.模板
- 1.
WXML
提供模板(template
),可以在模板中定义代码片段,然后在不同的地方调用
1.定义模板
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} text>
<text> Time: {{time}} text>
view>
template>
2.使用模板
<template is="msgItem" data="{{...item}}"/>
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
3.动态模板
<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>
- 1.
template
的is
属性可以使用Mustache
语法,来动态决定具体需要渲染哪个模板
- 2.注意需要带上
双引号
4.模板的作用域
- 1.模板拥有自己的作用域,只能使用
data
传入的数据以及模板定义文件中定义的
模块
5.引用
- 1.
WXML
提供两种文件引用方式import
和include
1.import
<template name="item">
<text>{{text}}text>
template>
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
- 1.
import
可以在该文件中使用目标文件定义的template
1.import 的作用域
<template name="A">
<text> A template text>
template>
<import src="a.wxml"/>
<template name="B">
<text> B template text>
template>
<import src="b.wxml"/>
<template is="A"/>
<template is="B"/>
- 1.
import
有作用域的概念,即只会import
目标文件中定义的template
,而不会 import
目标文件import
的template
2.include
<include src="header.wxml"/>
<view> body view>
<include src="footer.wxml"/>
<view> header view>
<view> footer view>
- 1.
include
可以将目标文件除了
外的整个代码引入,相当于是拷贝到include
位置
WXSS
- 1.
WXSS (WeiXin Style Sheets)
是一套样式语言,用于描述WXML
的组件样式
- 2.
WXSS
具有CSS
大部分的特性,小程序在WXSS
也做了一些扩充和修改
- 1.新增尺寸单位
- 1.写
CSS
样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位
- 2.
WXSS
在底层支持新的尺寸单位 rpx
,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差
- 2.提供全局的样式和局部样式
- 1.类似
app.json, page.json
,可以写一个app.wxss
作为全局样式,会作用于当前小程序的所有页面
- 2.局部页面样式
page.wxss
仅对当前页面生效
- 3.此外
WXSS
仅支持部分CSS
选择器
1.尺寸单位
设备 |
rpx换算px (屏幕宽度/750) |
px换算rpx (750/屏幕宽度) |
iPhone5 |
1rpx = 0.42px |
1px = 2.34rpx |
iPhone6 |
1rpx = 0.5px |
1px = 2rpx |
iPhone6 Plus |
1rpx = 0.552px |
1px = 1.81rpx |
- 1.
rpx(responsive pixel)
- 1.可以根据屏幕宽度进行自适应,规定屏幕宽为
750rpx
- 2.例:
iPhone6
屏幕宽度为375px
,共有750
个物理像素,则750rpx
= 375px
= 750物理像素
,1rpx
= 0.5px
= 1物理像素
- 2.建议开发微信小程序时可以用
iPhone6
作为视觉稿的标准
- 3.注意:较小的屏幕上不可避免的会有一些毛刺,开发时尽量避免这种情况
2.样式导入
.small-p {
padding:5px;
}
@import "common.wxss";
.middle-p {
padding:15px;
}
- 1.使用
@import
语句可以导入外联样式表
- 2.
@import
后跟需要导入的外联样式表的相对路径
,用;
表示语句结束
3.内联样式
<view style="color:{{color}};" />
<view class="normal_view" />
- 1.框架组件上支持使用
style
、class
属性来控制组件的样式
- 1.
style
:静态的样式统一写到class
中,style
接收动态的样式
,在运行时会进行解析,尽量避免将静态的样式写进style
中,以免影响渲染速度
- 2.
class
:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上.
,样式类名之间用空格
分隔
1.目前支持的选择器
选择器 |
样例 |
样例描述 |
.class |
.intro |
选择所有拥有 class=“intro” 的组件 |
#id |
#firstname |
选择拥有 id=“firstname” 的组件 |
element |
view |
选择所有 view 组件 |
element, element |
view, checkbox |
选择所有文档的 view 组件和所有的 checkbox 组件 |
::after |
view::after |
在 view 组件后边插入内容 |
::before |
view::before |
在 view 组件前边插入内容 |
4.全局样式与局部样式
- 1.定义在
app.wxss
中的样式为全局样式,作用于每一个页面
- 2.
page
的wxss
文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖app.wxss
中相同的选择器
JS
- 1.微信小程序通过编
JS
脚本文件处理用户的操作,和用户做交互
1.事件
- 1.事件是视图层到逻辑层的通讯方式
- 2.事件可以将用户的行为反馈到逻辑层进行处理
- 3.事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数
- 4.事件对象可以携带额外信息(例:
id
, dataset
, touches
)
1.事件分类
- 1.事件分为
- 1.
冒泡事件
:当一个组件上的事件被触发后,该事件会向父节点传递
类型 |
触发条件 |
最低版本 |
touchstart |
手指触摸动作开始 |
|
touchmove |
手指触摸后移动 |
|
touchcancel |
手指触摸动作被打断,如来电提醒,弹窗 |
|
touchend |
手指触摸动作结束 |
|
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 |
- 2.
非冒泡事件
:当一个组件上的事件被触发后,该事件不会向父节点传递
- 1.除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件
2.普通事件绑定
<view bindtap="handleTap">
Click here!
view>
<view bindtap="{{ handlerName }}">
Click here!
view>
- 1.事件绑定的写法类似于组件的属性 ,如果用户点击
view
组件,则页面的handleTap
会被调用
- 2.事件绑定函数可以是一个数据绑定,此时页面的
this.data.handlerName
必须是一个字符串,用于指定事件处理函数名
- 3.如果
this.data.handlerName
是空字符串,则这个绑定会失效(可利用该特性来暂时禁用一些事件)
- 4.自基础库版本
1.5.0
起,大多数组件和自定义组件中, bind
后可跟一个冒号但其含义不变(例:bind:tap
),自基础库版本2.8.1
起所有组件都开始提供这个支持
3.互斥事件绑定
<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.自基础库版本
2.8.2
起,除bind
和catch
外还可使用mut-bind
绑定事件
- 2.
mut-bind
触发后,如果事件冒泡到其他节点上,则其他节点上的mut-bind
绑定函数不会被触发,但bind
绑定函数和 catch
绑定函数依旧会被触发
- 3.即所有
mut-bind
是互斥的,只会有其中一个绑定函数被触发,同时不影响bind
和catch
的绑定效果
- 4.例:上述代码中点击
inner view
会先后调用handleTap3
和handleTap2
,点击middle view
会调用handleTap2
和handleTap1
4.绑定并阻止事件冒泡
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
view>
view>
view>
handleTap1: function(event) {
console.log('handleTap1')
console.log(event)
},
handleTap2: function(event) {
console.log('handleTap2')
console.log(event)
},
handleTap3: function(event) {
console.log('handleTap3')
console.log(event)
},
- 1.除
bind
外也可以用catch
来绑定事件,与bind
不同catch
会阻止事件向上冒泡
- 2.例:上述代码点击
inner view
会先后调用handleTap3
和handleTap2
- 1.因为
tap
事件会冒泡到middle view
,而middle view
中的catch
阻止了tap
事件冒泡,不再向父节点传递
- 2.点击
middle view
会触发handleTap2
,点击outer view
会触发handleTap1
5.事件的捕获阶段
<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>
<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>
- 1.自基础库版本
1.5.0
起,触摸类事件支持捕获阶段
- 2.
捕获阶段
位于冒泡阶段
之前,捕获阶段中事件到达节点的顺序与冒泡阶段恰好相反
- 3.捕获阶段监听事件时可采用
capture-bind
、capture-catch
关键字,注意capture-catch
将中断捕获阶段并取消冒泡阶段
- 4.例:上述代码中
- 1.点击
inner view
会先后调用handleTap2
、handleTap4
、handleTap3
、handleTap1
- 2.如果将第一个
capture-bind
改为capture-catch
,将只触发handleTap2
2.事件对象
- 1.如果无特殊说明当
组件触发事件
时,逻辑层绑定该事件的处理函数
会收到一个事件对象
1.BaseEvent 基础事件对象属性列表
属性 |
类型 |
说明 |
基础库版本 |
type |
String |
事件类型 |
|
timeStamp |
Integer |
事件生成时的时间戳,即页面打开到触发事件所经过的毫秒数 |
|
target |
Object |
触发事件的组件的一些属性值集合/触发事件的源组件 |
|
currentTarget |
Object |
当前组件的一些属性值集合 |
|
mark |
Object |
事件标记数据 |
|
-
1.target
说明
属性 |
类型 |
说明 |
id |
String |
事件源组件的id |
dataset |
Object |
事件源组件上由data-开头的自定义属性组成的集合 |
-
2.currentTarget
说明
属性 |
类型 |
说明 |
id |
String |
当前组件的id |
dataset |
Object |
当前组件上由data-开头的自定义属性组成的集合 |
-
3.id
说明:
- 1.
target
和currentTarget
可参考点击inner view
时handleTap3
收到的事件对象target
和currentTarget
都是 inner
- 2.而
handleTap2
收到的事件对象target
是innercurrentTarget
是middle
-
4.dataset
说明:
- 1.组件节点中可附加一些自定义数据,事件中可获取自定义的节点数据用于事件的逻辑处理
- 2.
WXML
中自定义数据以data-
开头,多个单词由连字符-
连接
- 3.注意:
连字符
写法会转换成驼峰
写法,而大写
字符会自动转成小写
字符<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
}
})
- 1.
data-element-type
:最终会呈现为event.currentTarget.dataset.elementType
- 2.
data-elementType
:最终会呈现为event.currentTarget.dataset.elementtype
-
5.mark
说明:
<view mark:myMark="last" bindtap="bindViewTap">
<button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮button>
view>
Page({
bindViewTap: function(e) {
e.mark.myMark === "last"
e.mark.anotherMark === "leaf"
}
})
- 1.基础库版本
2.7.1
以上可使用mark
识别具体触发事件的target
节点,此外mark
还可用于承载一些自定义数据(类似于 dataset
)
- 2.当事件触发时,事件冒泡路径上所有的
mark
会被合并,并返回给事件回调函数(即使事件不是冒泡事件也会mark
)
- 3.
mark
和 dataset
区别:
- 1.
mark
会包含从触发事件的节点到根节点上所有的mark:属性值
- 2.
dataset
仅包含一个节点的data-属性值
- 4.
mark
注意事项
- 1.如果存在同名的
mark
,父节点的mark
会被子节点覆盖。
- 2.自定义组件中接收事件时,
mark
不包含自定义组件外的节点的mark
- 3.不同于
dataset
,节点的mark
不会做连字符和大小写转换
2.CustomEvent 自定义事件对象属性列表(继承BaseEvent)
属性 |
类型 |
说明 |
detail |
Object |
额外的信息 |
- 1.
detail
说明:
- 1.
detail
是自定义事件所携带的数据(例:表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息)
- 2.点击事件的
detail
带有的 x, y
同pageX, pageY
代表距离文档左上角的距离
3.TouchEvent 触摸事件对象属性列表(继承BaseEvent)
属性 |
类型 |
说明 |
touches |
Array |
触摸事件,当前停留在屏幕中的触摸点信息的数组 |
changedTouches |
Array |
触摸事件,当前变化的触摸点信息的数组 |
- 1.
touches
说明
属性 |
类型 |
说明 |
identifier |
Number |
触摸点的标识符 |
pageX, pageY |
Number |
距离文档左上角的距离,文档的左上角为原点 ,横向为X轴,纵向为Y轴 |
clientX, clientY |
Number |
距离页面可显示区域(屏幕除去导航条)左上角距离,横向为X轴,纵向为Y轴 |
- 1.
touches
是一个数组,每个元素为一个Touch
对象,表示当前停留在屏幕上的触摸点
- 2.
changedTouches
说明
- 1.
changedTouches
数据格式同touches
,表示有变化的触摸点
- 1.从无变有(
touchstart
)
- 2.位置变化(
touchmove
)
- 3.从有变无(
touchend、touchcancel
)
- 3.
CanvasTouch
说明
属性 |
类型 |
说明 |
identifier |
Number |
触摸点的标识符 |
x, y |
Number |
距离 Canvas 左上角的距离,Canvas 的左上角为原点 ,横向为X轴,纵向为Y轴 |
- 1.
canvas
触摸事件中携带的touches
是CanvasTouch
数组
- 2.注意:
canvas
中的触摸事件不可冒泡,所以没有 currentTarget
3.使用方式
<view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! ></view>
Page({
tapName: function(event) {
console.log(event)
}
})
- 1.组件中绑定一个事件处理函数,当用户点击该组件时会在该页面对应的
Page
中找到相应的事件处理函数
1.点击事件
<view>{{ msg }}view>
<button bindtap="clickMe">点击我button>
Page({
clickMe: function() {
this.setData({ msg: "Hello World" })
}
})
- 1.点击
button
按钮时希望把界面上msg
显示成Hello World
- 2.因此在
button
上声明一个属性bindtap
并在JS
文件里声明clickMe
方法响应点击操作
- 3.此外还可在
JS
中调用小程序提供的API
,利用API
可以方便的调用微信提供的能力(例:获取用户信息wx.getUserInfo
、本地存储、微信支付等)
1.使用WXS函数响应事件
<wxs module="wxs" src="./test.wxs">wxs>
<view id="tapTest" data-hi="Weixin" bindtap="{{wxs.tapName}}"> Click me! view>
function tapName(event, ownerInstance) {
console.log('tap Weixin', JSON.stringify(event))
}
module.exports = {
tapName: tapName
}
- 1.从基础库版本
2.4.4
开始,支持使用WXS
函数绑定事件(基础库2.4.4
开始支持,低版本需做兼容处理)
- 2.
WXS
函数接受2
个参数,第一个是event
,在原有的event
的基础上加了event.instance
对象,第二个参数是ownerInstance
,和event.instance
一样是一个ComponentDescriptor
对象
- 3.
ownerInstance
包含了一些方法,可以设置组件的样式和class
组件