一个小程序主体部分由三个文件组成,必须放在项目的根目录
没有app.wxml文件,小程序的首页是由app.json文件的“pages”字段配置。小程序所有的页面必须在app.json声明才可以。
文件类型说明:
JSON 是一种数据格式,在小程序中,JSON扮演的静态配置的角色。小程序是无法在运行过程中去动态更新JSON 配置文件从而发生对应的变化的。
JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据。JSON的Key必须包裹在一个双引号中。 无法使用注释,试图添加注释将会引发报错。
值只能是以下几种数据格式
全局配置:包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。[官方文档]JSON-配置
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
pages 接受一个数组(使用中括号[]),每一项都是字符串(双引号),来指定小程序由哪些页面组成。每一项代表对应页面的【路径+文件名】信息,数组的第一项代表小程序的初始页面。 小程序中新增/减少页面,都需要对 pages 数组进行修改。
window用于设置小程序的状态栏、导航条、标题、窗口背景色。[w3cschool]小程序配置
工具配置 :工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等等一系列选项。开发者工具的配置
页面配置:用来表示 pages/logs 目录下的 logs.json 这类和小程序页面相关的配置。
可能你小程序里边的每个页面都有不一样的色调来区分不同功能模块,因此提供了 page.json,让开发者可以独立定义每个页面的一些属性。 页面配置
page.json中配置项会覆盖 app.json 的 window 中相同的配置项
page.json只能设置window相关的配置项,以决定本页面的窗口表现,所以无需写window这个键。小程序配置
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能演示",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
WXML由标签、属性等等构成,充当的就是类似 HTML 的角色。不同之处
wx:if 这样的属性以及 { { } } 这样的表达式<view class="container"> <view class="userinfo"> <button wx:if="{{!hasUserInfo && canIUse}}"> 获取头像昵称 </button> <block wx:else> <image src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </block> </view> <view class="usermotto"> <text class="user-motto">{{motto}}</text> </view> </view>
小程序采用MVVM的开发模式,提倡把渲染和逻辑分离。简单来说就是不要再让JS直接操控DOM,JS只需要管理状态即可,然后再通过一种模板语法来描述状态和界面结构的关系即可。
wxml这么写
<text>{{ msg }}</text>
JS 只需要管理状态即可:
this.setData({ msg: "Hello World" })
通过{{}}的语法把一个变量绑定到界面上,我们称为数据绑定。
更多绑定参考微信小程序 数据绑定
<!--wxml--> <text>{{message}}</text>
// page.js Page({ data: { message: 'Hello MINA!' } })
(需要在双引号之内)
<view id="item-{{id}}"> </view>
Page({ data: { id: 0 } })
(需要在双引号之内)
<view wx:if="{{condition}}"> </view>
Page({ data: { condition: true } })
<view>{{object.key}} {{array[0]}}</view>
Page({ data: { object: { key: 'Hello ' }, array: ['MINA'] } })
也可以用扩展运算符…来将一个对象展开
<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}
仅仅通过数据绑定还不够完整的描述状态和界面的关系,还需要 if/else, for等控制能力,在小程序里边,这些控制能力都用 wx: 开头的属性来表达。WXML
<input value="{{value}}" />
如果使用 this.setData({ value: 'leaf' }) 来更新 value ,this.data.value 和输入框的中显示的值都会被更新为 leaf ;但如果用户修改了输入框里的值,却不会同时改变 this.data.value 。
如果需要在用户输入的同时改变 this.data.value ,需要借助简易双向绑定机制。此时,可以在对应项目之前加入 model: 前缀
<input model:value="{{value}}" />
双向绑定的表达式有如下限制:
只能是一个单一字段的绑定
在组件 view 上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。[官方文档 列表渲染]
默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
<view wx:for="{{array}}"> {{index}}: {{item.message}} </view>
Page({ data: { array: [{ message: 'foo', }, { message: 'bar' }] } })
输出:
0:foo 1:bar
使用 wx:for-item 可以指定数组当前元素的变量名,
使用 wx:for-index 可以指定数组当前下标的变量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName"> {{idx}}: {{itemName.message}} </view>
输出:
0:foo 1:bar
当 wx:for 的值为字符串时,会将字符串解析成字符串数组
<view wx:for="array"> {{item}} </view>
等同于
<view wx:for="{{['a','r','r','a','y']}}"> {{item}} </view>
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 <input/> 中的输入内容,<switch/> 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符。
wx:key 的值以两种形式提供
当数据改变触发渲染层重新渲染的时候,会校正带有 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: 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 }) } })
Math.random() 函数返回一个浮点, 伪随机数在范围从0到小于1
Math.floor() 返回小于或等于一个给定数字的最大整数。
concat() 方法用于连接两个或多个数组,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
在框架中,我们用 wx:if=“{ {condition} }” 来判断是否需要渲染该代码块:微信小程序 条件渲染 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>
因为wx:if是一个控制属性,需要将它添加到一个标签上。
但是如果我们想一次性判断多个组件标签,我们可以使用一个 block/ 标签将多个组件包装起来,并在上边使用wx:if控制属性。
<block wx:if="{{true}}"> <view> view1 </view> <view> view2 </view> </block>
WXSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。WXSS
/** common.wxss 局部样式 **/ .small-p { padding:5px; }
使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。
/** app.wxss 全局样式**/ @import "common.wxss"; .middle-p { padding:15px; }
*style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。
<view style="color:{{color}};" />
*class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上.,样式类名之间用空格分隔。
<view class="normal_view" />
用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作。
编写 JS 脚本文件来处理用户的操作。WXML - 事件
<!--app.wxml--> <view>{ { msg } }</view> <button bindtap="clickMe">点击我</button>
点击 button 按钮的时候,我们希望把界面上 msg 显示成 “Hello World”,于是我们在 button 上声明一个属性: bindtap ,在 JS 文件里边声明了 clickMe 方法来响应这次点击操作:
//app.js Page({ clickMe: function() { this.setData({ msg: "Hello World" }) } })
在组件中绑定一个事件处理函数。
如bindtap,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。
<!--index.wxml--> <view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>
//与index.wxml文件同目录下index.js,参数是event。 Page({ tapName: function(event) { console.log(event) } })
console.log(event)打印数据
{ "type": "tap", "timeStamp":895, "target": { "id": "tapTest", "dataset": { "hi": "WeChat" } }, "currentTarget": { "id": "tapTest", "dataset": { "hi": "WeChat" } }, "detail": { "x":53, "y":14 }, "touches": [{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14, }], "changedTouches": [{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14, }], }
事件分为冒泡事件和非冒泡事件
WXML的冒泡事件列表:
| 类型 | 触发条件 |
|---|---|
| touchstart | 手指触摸动作开始 |
| touchmove | 手指触摸后移动 |
| touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
| touchend | 手指触摸动作结束 |
| tap | 手指触摸后马上离开 |
| longtap | 手指触摸后,超过350ms再离开 |
注:除上表之外的其他组件自定义事件都是非冒泡事件,如<form/>的submit事件,<input/>的input事件,<scroll-view/>的scroll事件,(详见各个组件)
事件绑定的写法同组件的属性,以key、value的形式。
bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
<view id="outter" bindtap="handleTap1"> outer view <view id="middle" catchtap="handleTap2"> middle view <view id="inner" bindtap="handleTap3"> inner view </view> </view> </view>
如在上边这个例子中,点击inner view会先后触发handleTap3和handleTap2(因为tap事件会冒泡到middle view,而middle view阻止了tap事件冒泡,不再向父节点传递),点击middle view会触发handleTap2,点击outter view会触发handleTap1。
mut-bind 来绑定事件。一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发。
<view bindtap="{{ handlerName }}"> Click here! </view>
捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。
可以采用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>
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
| 属性 | 类型 | 说明 |
|---|---|---|
| type | String | 事件类型 |
| timeStamp | Integer | 事件生成时的时间戳 |
| target | Object | 触发事件的组件的一些属性值集合 |
| currentTarget | Object | 当前组件的一些属性值集合 |
target 触发事件的源组件。
| 属性 | 类型 | 说明 |
|---|---|---|
| id | String | 事件源组件的id |
| tagName | String | 当前组件的类型 |
| dataset | Object | 事件源组件上由data-开头的自定义属性组成的集合 |
currentTarget 事件绑定的当前组件。
| 属性 | 类型 | 说明 |
|---|---|---|
| id | String | 当前组件的id |
| tagName | String | 当前组件的类型 |
| dataset | Object | 事件源组件上由data-开头的自定义属性组成的集合 |
说明: target 和 currentTarget 可以参考上例中,点击 inner view 时,handleTap3 收到的事件对象 target 和 currentTarget 都是 inner,而 handleTap2 收到的事件对象 target 就是 inner,currentTarget 就是 middle。
dataset
在组件中可以定义数据,这些数据将会通过事件传递给 SERVICE。书写方式:以data-开头,多个单词由连字符-链接,不能有大写(大写会自动转成小写)如data-element-type,最终在 event.target.dataset 中会将连字符转成驼峰elementType。
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({ bindViewTap:function(event){ event.target.dataset.alphaBeta === 1 // - 会转为驼峰写法 event.target.dataset.alphabeta === 2 // 大写会转为小写 } })
| 属性 | 类型 | 说明 |
|---|---|---|
| detail | Object | 额外的信息 |
detail
自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。
点击事件的detail 带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。
| 属性 | 类型 | 说明 |
|---|---|---|
| touches | Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 |
| changedTouches | Object | 触摸事件,当前变化的触摸点信息的数组 |
小程序根目录下的 sitemap.json 文件用来配置小程序及其页面是否允许被微信索引。[官方文档]sitemap 配置
//所有页面都会被微信索引(默认情况)
{
"rules":[{
"action": "allow", //允许
"page": "*"
}]
}
//配置 path/to/page 页面被索引,其余页面不被索引
{
{
"rules":[{
"action": "allow", //允许
"page": "path/to/page"
}, {
"action": "disallow",//不允许
"page": "*"
}]
}
}