使用"开发者工具+JS-基础模板"可以创建小程序的基础运行环境,但并不能亲身体验代码从0起始到100结束的快乐,因为代码都是借助模板自动生成的,并且自动生成的代码太多太繁琐,对初学者不太友好,也不能从最简的角度概括一个小程序的最简配置,所以本章节以手动的方式搭建一个HelloWorld小程序,借助"JS-基础模板"创建的项目,但不借助"JS-基础模板"生成的代码,所有程序代码均采用手动编写的方式(代码量非常少),从0起始搭建一个最简的小程序,目的是了解一个最简小程序的最简必备配置有哪些。
在开发者工具中创建类型为“小程序”的项目,如图2-xx所示。
图2-xx 项目类型为小程序
项目配置如图2-xx所示。
图2-xx 创建项目的配置
项目创建成功后,在index文件夹中默认有4个文件,手动删除这4个文件,如图2-xx所示。
图2-xx 删除4个文件
4个文件成功删除后,index文件夹中的内容为空,如图2-xx所示。
图2-xx 空的index文件夹
由于没有WXML文件,模拟器出现异常,如图2-xx所示。
图2-xx 模拟器出现异常
本章节会对这4种文件类型做简单的介绍,建议大家读写并行,从而对小程序的代码组成有一个大致的理解。
在index文件夹点击鼠标右键选中“新建Page”菜单,如图2-xx所示。
图2-xx 新建Page
指定Page名称为index,如图2-xx所示。
图2-xx 指定Page名称
成功批量创建了4个文件,1个Page页需要这4个文件,如图2-xx所示。
图2-xx 成功创建4个文件
2.1.2.1 编辑index.js文件
代码如下:
// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
"username": "高洪岩"
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
在data字段中添加名称为username的属性和其对应的值"高洪岩"。
2.1.2.2 编辑index.json文件
代码如下:
{
"usingComponents": {}
}
代码内容为默认,没有经过后期的手动编辑。
2.1.2.3 编辑index.wxml文件
代码如下:
<text class="usernameClass">你好:{{username}}</text>
使用<text>标签显示文字,添加了类样式的class属性。最为关键的是使用{{}}格式显示username属性的值"高洪岩"。
2.1.2.4 编辑index.wxss文件
代码如下:
.usernameClass {
font-size: 30px;
color: red;
}
创建一个新的类样式。
模拟器中的运行效果如图2-xx所示。
图2-xx 模拟器中运行
真机中的运行效果如图2-xx所示。
图2-xx 真机中运行
成功搭建一个从0到100的小程序项目并且正常运行。
创建名称为batchCreate4File的小程序项目。
在app.json文件的"pages/index/index"配置的上方添加新的一行配置:
"pages/newpage/index",
保存app.json文件后可以在编辑器中看到自动创建的4种文件类型,如图2-xx所示。
图2-xx 自动创建4个文件
小程序由配置代码JSON文件、模板代码WXML文件、样式代码WXSS文件以及逻辑代码JavaScript文件所组成。本章节简单介绍这4种类型的文件中常用代码的写法,建议一边学习一边上机同步进行测试,从而对小程序的代码组成有一个大致的理解。
本章节演示的代码属于高频使用的技术内容,建议仔细学习和理解。
JSON全称JavaScript Object Notation,译为“JS对象标记”,它是一种轻量级的数据交换格式。JSON是一种数据格式,并不是一种编程语言。在小程序中,JSON可以扮演两种角色:
(1)静态配置
(2)数据交换
2.2.1.1 JSON的作用
JSON的作用:
(1)有利于程序员阅读和编写
(2)有利于计算机解析和生成
(3)能够有效提升网络传输效率
(4)适用于前后端数据交换
2.2.1.2 JSON的一个小例子
在小程序中,JSON可以扮演静态配置的角色。
创建名称为changeNavigationBarTitleText的小程序项目并找到app.json文件,静态配置代码如下:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "Weixin",
"navigationBarBackgroundColor": "#ffffff"
},
"style": "v2",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}
更改静态配置代码如下:
"navigationBarTitleText": "我的小程序的新的标题"
运行小程序效果如图2-xx所示。
图2-xx 新的小程序的标题
JSON文件在小程序代码中可以扮演静态配置的作用,在小程序运行之前就决定了小程序的一些表现。需要注意的是,小程序是无法在运行过程中动态更新JSON静态配置文件从而发生对应的变化。
2.2.1.3 JSON语法
相比于XML,JSON格式最大的优点就是易于人的阅读和编写,通常不需要特殊的工具就能读懂和修改,是一种轻量级的数据交换格式。
JSON语法规则如图2-xx所示。
图2-xx JSON语法规则
JSON对象和数组的示例代码如图2-xx所示。
图2-xx 示例代码
JSON对象是被包裹在一个{}大括号中,通过key-value键值对的方式来表达数据,效果如图2-xx所示。
图2-xx JSON格式的示例
看起来同JavaScript的对象表达方式十分相似,但是有所不同:
JSON的key必须包裹在一个""双引号中。
在实践中编写JSON的时候,忘了给key值加双引号或者把双引号写成单引号是常见错误,如图2-xx所示。
图2-xx 无双引和使用单引的错误
JSON的值只能是以下几种数据类型:
(1)数字,包含浮点数和整数
(2)字符串,需要包裹在双引号中
(3)Bool值,true或者false
(4)数组,需要包裹在[]方括号中
(5)对象,需要包裹在{}大括号中
(6)Null
其他任何数据类型都会触发报错,例如JavaScript中的undefined,效果如图2-x所示。
图2-x 值不能是undefined
还需要注意的是,JSON文件中无法使用注释,试图添加注释将会引发报错,效果如图2-xx所示。
图2-xx JSON中不允许有注释
2.2.1.4 JSON字符串转换成JSON对象并解析
创建名称为string_to_object_parse的小程序项目。
示例代码如下:
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const jsonString = '{"a":"我是字符串","b":123,"c":false,"d":[1,2,3],"e":{"e1":"高洪岩","e2":"100"},"f":null}';
const jsonObject = JSON.parse(jsonString);
console.log(jsonObject.a);
console.log(jsonObject.b);
console.log(jsonObject.c);
console.log(jsonObject.d[0] + " " + jsonObject.d[1] + " " + jsonObject.d[2]);
console.log(jsonObject.e.e1 + " " + jsonObject.e.e2);
console.log(jsonObject.f);
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.1.5 JSON对象转换成JSON字符串
创建名称为object_string的小程序项目。
示例代码如下:
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const jsonObject = { "a": "我是字符串", "b": 123, "c": false, "d": [1, 2, 3], "e": { "e1": "高洪岩", "e2": "100" }, "f": null };
console.log(JSON.stringify(jsonObject) + " zzzzzzz");
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.1.6 直接解析JSON对象
创建名称为parseJSON的小程序项目。
示例代码如下:
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const jsonObject = { "a": "我是字符串", "b": 123, "c": false, "d": [1, 2, 3], "e": { "e1": "高洪岩", "e2": "100" }, "f": null };
console.log(jsonObject.a);
console.log(jsonObject.b);
console.log(jsonObject.c);
console.log(jsonObject.d[0] + " " + jsonObject.d[1] + " " + jsonObject.d[2]);
console.log(jsonObject.e.e1 + " " + jsonObject.e.e2);
console.log(jsonObject.f);
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
WXML全称是WeiXin Markup Language,它是为小程序框架设计的一套标记语言,结合小程序的基础组件、事件系统,可以构建出页面的结构。
2.2.2.1 介绍
WXML文件的后缀名是.wxml,有过HTML开发经验的朋友应该会很熟悉这种代码的书写方式,简单的WXML语句在语法上同HTML非常相似,示例代码如下:
<!--pages/index/index.wxml-->
<text>pages/index/index.wxml</text>
不带有任何逻辑功能的WXML基本语法如下:
<!-- 在此处写注释 -->
<标签名 属性名1="属性值1" 属性名2="属性值2" ...>...</标签名>
一个完整的WXML语句由开始标签和结束标签组成。在标签中可以是内容,也可以是其它的WXML标签,这一点同HTML是一致的,示例代码如下:
<!--一个简单的文本标签 -->
<text>hello world</text>
<!-- view 中包含了text标签 -->
<view>
<text>hello world</text>
</view>
WXML与HTML有所不同的是,WXML要求标签必须是严格闭合的,没有闭合的标签将会导致编译错误。创建名称为halfTag的小程序项目,测试代码如图2-xx所示。
图2-xx 编译错误
标签可以有属性,属性提供了WXML元素的更多信息。属性总是定义在开始标签中,除了一些特殊的属性外,其余属性的格式都是key="value"的方式成对出现的,示例代码如下:
<image class="userinfo-avatar" src="./image/a.png"></image>
需要注意的是,WXML中的属性是大小写敏感的,也就是说class和Class在WXML中是不同的两个属性。
2.2.2.2 数据绑定
在用户界面中呈现的内容会因为当前数据的不同而有所不同,或者是因为用户的操作而发生动态的改变,这就要求程序在运行的过程中要有动态渲染改变界面的能力。在Web开发中,开发者使用JavaScript通过Dom接口来完成界面的实时更新,而在小程序中,使用WXML语言所提供的数据绑定功能来完成此功能。
先来看一个数据绑定的简单例子。创建名称为showTime的小程序项目,将WXML文件的内容做一些简单的修改,示例代码如下:
<text>当前时间:{{time}}</text>
保存文件并编译,模拟器并没有显示出当前的时间,如图2-xx所示。
图2-xx 未显示时间
这是因为并没有给time属性设置任何的初始值。打开JS文件,编辑data代码段如下:
data: {
time: (new Date()).toString(),
},
保存文件并编译,模拟器刷新后正确显示当前的时间,并且每次编译后的时间都会被更新,运行效果如图2-xx所示。
图2-xx 正确显示时间
WXML通过{{变量名}}来绑定WXML文件和对应的JavaScript文件中的data对象属性,形成映射关系。
属性值也可以动态的改变,需要注意的是,属性值必须被包裹在双引号中。创建名称为doubleQuotationMarks_Brace的小程序项目。
JS示例代码如下:
data: {
test1: "test1Value",
test2: "test2Value"
}
WXML示例代码更改如下:
<!-- 正确的写法 -->
<text data-test="{{test1}}">hello world 1</text>
<!-- 错误的写法 -->
<!-- <text data-test={{test2}}>hello world 2</text> -->
正确的示例代码的测试结果如图2-xx所示。
图2-xx 运行效果
WXML示例代码更改如下:
<!-- 正确的写法 -->
<!--<text data-test="{{test1}}">hello world 1</text>-->
<!-- 错误的写法 -->
<text data-test={{test2}}>hello world 2</text>
错误的示例代码的测试结果如图2-xx所示。
图2-xx 运行效果
需要注意的是,变量名是大小写敏感的,也就是说{{name}}和{{Name}}是两个不同的变量。创建名称为caseSensitive的小程序项目。
WXML示例代码如下:
<text class="{{myclass}}">text1</text>
<text class="{{myClass}}">text2</text>
<text class="{{myABC}}">text3</text>
JS示例代码如下:
data: {
myclass: "myclassValue1",
myClass: "myClassValue2",
myabc: "myabcValue3"
}
大小写敏感的运行效果如图2-xx所示。
图2-xx 运行效果
还需要注意,没有被定义的变量或者是被赋值为undefined的变量是不会被同步到wxml文件中。创建名称为undefinedValueNoSyn的小程序项目。
WXML示例代码如下:
<view>{{var1}}</view>
<view>{{var2}}</view>
<view>{{var3}}</view>
<view>{{var4}}</view>
JS示例代码如下:
data: {
var2: undefined,
var3: null,
var4: "var4Value"
}
测试效果如图2-xx所示。
图2-xx 运行效果
关于数据绑定的相关知识在后面的章节中有更为详细的介绍。
2.2.2.3 逻辑语法
通过{{变量名}}语法可以让WXML拥有动态渲染的能力,除此之外还可以在{{}}里面进行简单的逻辑运算。
测试三元运算。创建名称为ternaryOperator的小程序项目。
WXML示例代码如下:
<!-- 根据a和b的值是否等于10在页面输出不同的内容 -->
<text>{{ a === 10? "变量 a 等于10": "变量 a 不等于10"}}</text>
<text>{{ b === 10? "变量 b 等于10": "变量 b 不等于10"}}</text>
JS示例代码如下:
data: {
a: 10,
b: 11
}
测试效果如图2-xx所示。
图2-xx 运行效果
测试算数运算。创建名称为arithmeticOperation的小程序项目。
WXML示例代码如下:
<view> {{a + b}} + {{c}} + d </view>
JS示例代码如下:
data: {
a: 1,
b: 2,
c: 3
}
测试效果如图2-xx所示。
图2-xx 运行效果
类似于算数运算,在{{}}中还支持字符串的拼接。
测试字符串拼接。创建名称为stringConcat的小程序项目。
WXML示例代码如下:
<view>{{"hello " + name}}</view>
JS示例代码如下:
data: {
name: 'world'
}
测试效果如图2-xx所示。
图2-xx 运行效果
测试在{{}}中还可以直接放置数字、字符串或者是数组。创建名称为numberStringArray的小程序项目。
WXML示例代码如下:
<text>{{123}}</text>
<text>{{"hello world"}}</text>
<text>{{[4,5,6]}}</text>
JS示例代码如下:
data: {
}
测试效果如图2-xx所示。
图2-xx 运行效果
2.2.2.4 条件逻辑
在WXML中,使用wx:if="{{condition}}"判断是否需要渲染该代码块。wx:if是一个控制属性,需要将它添加到一个标签上。
创建名称为wxifTest的小程序项目。
WXML示例代码如下:
<view wx:if="{{condition1}}"> A </view>
<view wx:if="{{condition2}}"> B </view>
JS示例代码如下:
data: {
condition1: true,
condition2: false
}
测试效果如图2-xx所示。
图2-xx 运行效果
使用wx:if和wx:elif和wx:else来添加一个else代码块。
创建名称为wxif_wxelif_wxelse的小程序项目。
WXML示例代码如下:
<view wx:if="{{length == 1}}">1</view>
<view wx:elif="{{length == 2}}">2</view>
<view wx:elif="{{length == 3}}">3</view>
<view wx:elif="{{length == 4}}">4</view>
<view wx:else>other</view>
JS示例代码如下:
data: {
length: 8
}
测试效果如图2-xx所示。
图2-xx 运行效果
如果要一次性判断多个组件标签,可以使用<block><block/>标签将多个组件包装起来,并在<block>开始标签中使用wx:if属性进行逻辑控制。
创建名称为blockTag_wxif的小程序项目。
WXML示例代码如下:
<block wx:if="{{value1}}">
<view>view1</view>
<view>view2</view>
</block>
---
<block wx:if="{{value2}}">
<view>view3</view>
<view>view4</view>
</block>
JS示例代码如下:
data: {
value1: true,
value2: false
}
测试效果如图2-xx所示。
图2-xx 运行效果
2.2.2.5 列表渲染
在组件上使用wx:for控制属性绑定一个数组,可以使用数组中的各项数据重复渲染该组件。默认数组的当前项的下标变量名为index,数组当前项的变量名为item。
创建名称为wxforTest的小程序项目。
WXML示例代码如下:
<!-- array 是一个数组 -->
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
JS示例代码如下:
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
测试效果如图2-xx所示。
图2-xx 运行效果
使用wx:for-index指定数组当前下标的变量名,使用wx:for-item指定数组当前元素的变量名。
创建名称为wxfor_index_item的小程序项目。
WXML示例代码如下:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
JS示例代码如下:
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
测试效果如图2-xx所示。
图2-xx 运行效果
类似<block>结合wx:if属性,也可以将wx:for属性用在<block>标签上,以渲染一个包含多个节点的结构块。
创建名称为blockTag_wxfor的小程序项目。
WXML示例代码如下:
<block wx:for="{{numberArray}}">
<view> {{index}}: </view>
<view> {{item}} </view>
--
</block>
JS示例代码如下:
data: {
numberArray: [1, 2, 3, 4, 5]
}
测试效果如图2-xx所示。
图2-xx 运行效果
如果列表(数组)中项目的位置会动态改变,比如有新的项目添加到列表中或者从列表中删除旧的项目,并且希望列表中的项目保持自己的特征和状态(例如<input/>的输入内容,<switch/>的选中状态),这时就得需要使用wx:key属性来指定列表中项目的唯一标识。
属性wx:key的值可以有两种形式:
(1)字符串:代表在for循环的array中的item的某个property,该property的值需要是列表中唯一的字符串或数字,且不能动态改变。
(2)*this:代表在for循环中的item本身,说明item本身是一个唯一的字符串或者数字。
当数据改变触发渲染层重新渲染的时候,会校正带有key属性的组件,框架会确保他们被重新排序,而不是重新创建,以确保组件保持自身的状态,并且提高列表渲染时的效率。前面这段话说明小程序默认是按照“就地更新”的策略来更新通过for渲染的元素列表,当数据项的顺序改变时,小程序不会随之移动DOM元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上进行渲染,增加响应的效率。
下面来测试一下,不使用属性wx:key会发生什么奇怪的问题。
创建名称为wxkeyNoExists的小程序项目。
WXML代码如下:
<div id="mydiv">
<block wx:for="{{myArray}}">
<input class="weui-input" value="{{item.message}}" />
--
</block>
</div>
JS代码如下:
Page({
data: {
myArray: [{ id: 1, message: 'A' }, { id: 2, message: 'B' }, { id: 3, message: 'C' }]
},
onReady() {
// 保存对this的引用
const that = this;
setTimeout(function () {
that.data.myArray.shift();
that.setData({ myArray: that.data.myArray });
}, 5000);
}
})
程序运行后,快速将光标焦点定位到<input type="text" value="B"/>单行文本域处。
5秒钟过后,第一个<input type="text" value="A"/>被删除,但光标位置是在<input type="text" value="C"/>处,不是理想中的<input type="text" value="B"/>处,单行文本域B没有“带着光标”,光标的位置是错误的,这就是不使用wx:key属性所发生的奇怪问题。
但默认模式(上面的效果)是高效的,不过只适用于列表渲染输出的结果不依赖子组件状态或者临时DOM状态(例如表单输入值)的情况。
为了给小程序一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素还保留组件的状态,需要为每个元素对应的块提供一个唯一的wx:key属性值。
创建名称为wxkeyHasExists的小程序项目。
更新后的WXML示例代码如下:
<div id="mydiv">
<block wx:for="{{myArray}}" wx:key="id">
<input class="weui-input" value="{{item.message}}" />
--
</block>
</div>
程序运行后,快速将光标焦点定位到<input type="text" value="B"/>处。
5秒钟过后,第一个<input type="text" value="A"/>被删除,但光标位置一直在<input type="text" value="B"/>处,而不是<input type="text" value="C"/>处,这才是理想中的运行效果。单行文本域B“带着光标了”。
下面再来看一下没有wx:key属性发生的又一起奇怪的问题。
创建名称为wxkey_NoExists的小程序项目。
WXML代码如下:
<switch wx:for="{{objectArray}}">{{item.id}}</switch>
<button bindtap="switch">Switch</button>
<button bindtap="addToFront">Add to the front</button>
<switch wx:for="{{numberArray}}"> {{item}} </switch>
<button bindtap="addNumberToFront"> Add Number to the front </button>
JS代码如下:
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
})
}
})
程序运行后,将checkbox的3和5的状态设置为checked,如图2-xx所示。
图2-xx 界面状态
多次点击Switch按钮,发现checked的状态有的时候并没有跟随checkbox的3和5,效果如图2-xx所示。
图2-xx 错误的checked跟随
重新运行程序,将checkbox的3和5的状态设置为checked,如图2-xx所示。
图2-xx 界面状态
多次点击Add to the front按钮后,checked的状态也是没有跟随checkbox的3和5,如图2-xx所示。
图2-xx 错误的checked跟随
重新运行程序,将checkbox的1和4设置为checked,如图2-xx所示。
图2-xx 界面状态
多次点击Add Number to the front按钮后的界面效果如图2-xx所示。
图2-xx 错误的checked跟随
出现这种奇怪问题的原因就是因为没有对wx:for属性结合使用wx:key属性。
创建名称为wxkey_HasExists的小程序项目。
更改WXML代码如下:
<switch wx:for="{{objectArray}}" wx:key="unique">{{item.id}}</switch>
<button bindtap="switch">Switch</button>
<button bindtap="addToFront">Add to the front </button>
<switch wx:for="{{numberArray}}" wx:key="*this">{{item}}</switch>
<button bindtap="addNumberToFront">Add Number to the front</button>
重新运行程序,发现checked的状态正确跟随了。
推荐在任何可行的时候为wx:for提供wx:key属性,除非所迭代的DOM内容非常简单(例如:不包含组件或者有状态的DOM元素),或者想有意采用默认行为来提高性能。
注意:建议wx:key绑定的值是一个基础数据类型的值,例如String字符串或number数字,不要用Object对象作为wx:key的值。
2.2.2.6 模板
WXML提供template模板技术,可以在template模板中定义代码片段,然后在不同的地方进行调用。
使用template模板大体有4个步骤:
创建名称为templateBasicUse的小程序项目。
WXML示例代码如下:
<template name="msgItem">
<view>
<text>{{index}}: {{msg}}</text>
<text>Time: {{time}}</text>
</view>
</template>
<template is="msgItem" data="{{...item}}" />
JS示例代码如下:
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-06-18'
}
}
})
程序运行效果如图2-xx所示。
图2-xx 程序运行效果
使用is属性可以动态决定具体需要渲染哪个模板。
创建名称为dynamicIsAttribute的小程序项目。
WXML示例代码如下:
<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>
程序运行效果如图2-xx所示。
图2-xx 程序运行效果
2.2.2.7 引用
WXML提供两种引用文件的方式:
2.2.2.7.1 使用import
import可以在WXML文件中使用目标WXML文件中定义的template。
创建名称为importTest的小程序项目。
在item.wxml文件中定义一个名称为item的template,示例代码如下:
<template name="item">
<text>{{text}}</text>
</template>
在index.wxml文件中使用import引用item.wxml模板后就可以使用item模板了。index.wxml示例代码如下:
<import src="item.wxml" />
<template is="item" data="{{text:'forbar'}}" />
项目结构如图2-xx所示。
图2-xx 项目结构
程序运行结果如图2-xx所示。
图2-xx 运行效果
需要注意的是,import有作用域的概念,就是只会import目标文件中定义的template,而不会import目标文件中import的template,简而言之就是import不具有递归import的特性。例如:C引用B,B引用A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template。下面做一个实验来证明一下。
创建名称为importScope的小程序项目。
文件a.wxml代码如下:
<!-- a.wxml -->
<template name="A">
<text>A template</text>
</template>
文件b.wxml代码如下:
<!--b.wxml-->
<import src="a.wxml" />
<template name="B">
<text>B template</text>
</template>
文件index.wxml代码如下:
<!--index.wxml等同于c.wxml-->
<import src="b.wxml" />
--
<template is="A" /><!--这里将会触发一个警告,因为b.wxml中并没有定义模板A-->
--
<template is="B" />
程序运行效果如图2-xx所示。
图2-xx 运行效果
触发警告的效果如图2-xx所示。
图2-xx 触发警告
文件index.wxml(c.wxml)并没有成功import导入模板A,因为import不支持递归import的功能。
2.2.2.7.2 使用include
include可以将目标wxml文件中除了<template/><wxs/>之外的整个代码引入,相当于拷贝到include的位置。
创建名称为includeTest的小程序项目。
index.wxml文件代码如下:
<!--index.wxml-->
<include src="header.wxml" />
<view>body</view>
<include src="footer.wxml" />
header.wxml文件代码如下:
<!--header.wxml-->
<view>header</view>
footer.wxml文件代码如下:
<!--footer.wxml-->
<view>footer</view>
程序运行结果如图2-xx所示。
图2-xx 运行效果
2.2.2.8 共同属性
所有wxml标签都支持的属性称为共同属性,如表2-xx所示。
属性名 | 类型 | 描述 | 注解 |
---|---|---|---|
id | String | 组件的唯一标识 | 整个页面唯一 |
class | String | 组件的样式类 | 在对应的 WXSS 中定义的样式类 |
style | String | 组件的内联样式 | 可以动态设置的内联样式 |
hidden | Boolean | 组件是否显示 | 所有组件默认显示 |
data-* | Any | 自定义属性 | 组件上触发的事件时,会发送给事件处理函数 |
bind*/catch* | EventHandler | 组件的事件 |
表2-xx 共同属性
WXSS(WeiXin Style Sheets)是一套用于小程序的样式语言,用于描述WXML的组件样式,也就是视觉上的效果。
WXSS与Web开发中的CSS类似。为了更适合小程序开发,WXSS对CSS做了一些补充以及修改。
2.2.3.1 文件组成
小程序中的WXSS样式文件的组成如图2-xx所示。
图2-xx 样式文件的组成
项目公共样式:根路径中的app.wxss文件是项目的公共样式文件,它会被注入到小程序的每个页面。
页面样式:与app.json注册过的页面同名且位置同级的WXSS文件。比如如图2-xx注册的pages/rpx/index页面,那么pages/rpx/index.wxss文件就是为页面pages/rpx/index.wxml提供的样式文件。
图2-xx 注册了pages/rpx/index页面
其它样式:其它样式可以被项目公共样式和页面样式引用,引用方法请参考后面有关的章节。
在小程序开发中,开发者不需要像Web开发那样去优化样式文件的请求数量,只需要考虑代码的组织即可。样式文件最终会被编译优化,具体的编译原理在后面的章节再做介绍。
2.2.3.2 尺寸单位
在WXSS中引入了rpx(responsive pixel)尺寸单位,引入该新尺寸单位的作用是适配不同宽度的屏幕,开发起来更简单。
使用px单位的运行效果如图2-xx所示,同一个元素,在不同宽度的屏幕下有可能造成页面留白过多。
图2-9 使用px尺寸单位在iPhone5与iPad的视觉对比
使用rpx单位的运行效果如图2-xx所示。
图2-xx 使用rpx尺寸单位在iPhone5与iPad的视觉对比
小程序编译后,rpx会做一次px换算。换算是以375个物理像素为基准,也就是在一个宽度为375的物理像素的屏幕下,1rpx = 1px。
举个例子,iPhone6的屏幕宽度为375px,共750个物理像素,那么1rpx = 375 / 750 px = 0.5px。
常用机型rpx尺寸换算表如图2-xx所示。
图2-xx 常用机型rpx尺寸换算表
2.2.3.3 WXSS引用
在CSS中使用下面代码可以引用另一个CSS样式文件:
@import url('./mod1.css')
这种写法在request请求上不会把mod1.css合并到index.css中,也就是在请求index.css文件的时候,会多一个mod1.css文件的请求,一共2个request请求,效果如图2-xx所示。
图2-xx 2个request请求
在小程序中依然可以实现样式的引用:
@import './mod1.wxss'
由于WXSS最终会被编译打包到目标文件中,用户只需要下载一次,在使用过程中不会因为样式的引用而产生多余的文件请求。
2.2.3.4 内联样式
WXSS内联样式与Web开发一致。
创建名称为inlineTest的小程序项目。
WXML示例代码更改如下:
<!--内联样式-->
<view style="color: red; font-size: 60rpx">我是文本</view>
程序运行效果如图2-xx所示。
图2-xx 运行效果
小程序支持动态更新内联样式。
创建名称为dynamicInline的小程序项目。
WXML代码如下:
<!--可动态变化的内联样式-->
<view style="color:{{eleColor}}; font-size:{{eleFontsize}}">我是文本</view>
JS代码如下:
Page({
data: {
eleColor: 'blue',
eleFontsize: '48rpx'
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.3.5 选择器
目前支持的选择器如表2-xx所示。
类型 | 选择器 | 样例 | 样例描述 |
---|---|---|---|
类选择器 | .class | .intro | 选择所有拥有 class="intro" 的组件 |
id选择器 | #id | #firstname | 选择拥有 id="firstname" 的组件 |
元素选择器 | element | view checkbox | 选择所有文档的 view 组件和所有的 checkbox 组件 |
伪元素选择器 | ::after | view::after | 在 view 组件后边插入内容 |
伪元素选择器 | ::before | view::before | 在 view 组件前边插入内容 |
表2-xx 小程序WXSS支持的选择器
WXSS优先级与CSS类似,权重如图2-xx所示。
图2-xx WXSS优先级排序
权重越高越优先。在优先级相同的情况下,后设置的样式优先级高于先设置的样式。
创建名称为updateStyle的小程序项目。
WXML代码如下:
<view id="ele" class="ele">我是文本</view>
WXSS代码如下:
view {
/*权重为1*/
color: blue
}
.ele {
/*权重为10*/
color: red
}
#ele {
/*权重为100*/
color: pink
}
view#ele {
/*权重为1+100=101,优先级最高,元素颜色为orange*/
color: orange
}
view.ele {
/*权重为1+10=11*/
color: green
}
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.3.6 官方样式库
为了减轻开发者样式开发的工作量,小程序平台官方提供了WeUI.wxss基础样式库。
WeUI是一套与微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,使用户的组件使用感知更加统一,包含button、cell、dialog、progress、toast、article、actionsheet、icon等等各种组件。
官方提供示例,如图2-xx所示。
图2-xx 官方示例
WeUI基础样式库的github仓库地址如下:
https://github.com/Tencent/weui-wxss
小程序的主要开发语言是JavaScript,开发者使用JavaScript来开发业务逻辑以及调用小程序的API来完成业务需求。
2.2.4.1 ECMAScript
在大部分开发者看来,ECMAScript和JavaScript表达的是同一种含义,但是严格来说,两者的意义是不同的。ECMAScript是一种由Ecma International通过ECMA-262规范标准化的脚本程序设计语言,JavaScript是ECMAScript的一种实现。理解JavaScript是ECMAScript一种实现后,可以帮助开发者理解小程序中的JavaScript同浏览器中的JavaScript以及NodeJS中的JavaScript是不相同的,JavaScript在不同平台中的差异:
(1)浏览器:提供DOM(操作网页元素)、BOM(操作浏览器窗口)等API。
(2)Node.js:提供文件系统(fs)、网络(http)等服务器端API。
(3)小程序:提供小程序生命周期(App()、Page())、组件通信、微信原生能力(支付、登录)等API。
ECMA-262规范规定了ECMAScript语言的几个重要组成部分:
(1)语法
(2)类型
(3)语句
(4)关键字
(5)操作符
(6)对象
浏览器中JavaScript的构成如图2-xx所示。
图2-xx 浏览器中JavaScript的构成
浏览器中的JavaScript是由ECMAScript和BOM(浏览器对象模型)以及DOM(文档对象模型)组成的,Web前端开发者会很熟悉这两个对象模型,它使得开发者可以去操作浏览器的一些表现,比如修改URL、修改页面呈现、记录数据等等。
NodeJS中JavaScript的构成如图2-xx所示。
图2-xx NodeJS中JavaScript的构成
NodeJS中的JavaScript是由ECMAScript和NPM以及Native模块组成,NodeJS的开发者会非常熟悉NPM的包管理系统,通过各种拓展包来快速的实现一些功能,同时通过使用一些原生的模块例如FS、HTTP、OS等等来拥有一些JavaScript语言本身所不具有的能力。
那么,同开发者所熟悉的这两个环境所不同的是,小程序中JavaScript的构成如图2-xx所示。
图2-xx 小程序中JavaScript的构成
小程序中的JavaScript是由ECMAScript以及小程序框架和小程序API来实现的。同浏览器中的JavaScript相比,没有BOM以及DOM对象,所以类似JQuery、Zepto这种浏览器类库是无法在小程序中运行起来的,同样也缺少Native模块和NPM包管理的机制,小程序中无法加载原生库,也无法直接使用大部分的NPM包。
2.2.4.2 小程序的执行环境
明白了小程序中的JavaScript同浏览器以及NodeJS有所不同之后,开发者还需要注意到另外一个问题,不同平台的小程序的脚本的执行环境也是有所区别的。
小程序目前可以运行在三大平台:
(1)iOS平台,包括iOS9、iOS10、iOS11
(2)Android平台
(3)小程序IDE
这种区别主要体现在三大平台实现ECMAScript的标准有所不同。截止到当前一共有七个版本的ECMAScript标准,目前开发者大部分使用的是ECMAScript 5和ECMAScript 6的标准,但是在小程序中,iOS9和iOS10所使用的运行环境并没有完全的兼容到ECMAScript 6标准,一些ECMAScript 6中规定的语法和关键字是没有的或者同标准是有所不同的,例如:
(1)箭头函数
(2)let const
(3)模板字符串
(4)…
所以一些开发者会发现有些代码在旧的手机操作系统上会出现一些语法错误。所以为了帮助开发者解决这类问题,小程序IDE提供语法转码工具来帮助开发者,将ECMAScript 6代码转换为ECMAScript 5代码,从而在所有的环境都能得到很好的执行。使用此功能需要在项目设置中勾选“ES6转ES5”来开启此功能,如图2-xx所示。
图2-xx 转换版本
2.2.4.3 模块化
在浏览器中,所有的JavaScript运行在同一个作用域下,定义的参数或者方法可以被后续加载的脚本访问或者改写。同浏览器不同,在小程序中可以将任何一个JavaScript文件作为一个模块,通过module.exports或者exports对外暴露接口,在需要使用这些模块的文件中,使用require(path)将公共代码引入即可。
创建名称为moduleExports_one1的小程序项目,实现使用module.exports导出1个函数的写法1。
模块文件myModule.js示例代码如下:
function testMethod1(numberValue) {
return numberValue + 10;
}
module.exports = testMethod1;
WXML示例代码如下:
<view>{{value1}}</view>
JS示例代码如下:
var testMethod1 = require('./myModule');
Page({
data: {
value1: testMethod1(1)
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
创建名称为moduleExports_one2的小程序项目,实现使用module.exports导出1个函数的写法2。
模块文件myModule.js示例代码如下:
function testMethod1(numberValue) {
return numberValue + 10;
}
module.exports = { testMethod1 };
WXML示例代码如下:
<view>{{value1}}</view>
JS示例代码如下:
var myModule = require('./myModule');
Page({
data: {
value1: myModule.testMethod1(1)
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
创建名称为exports_one的小程序项目,实现使用exports导出1个函数的写法。
模块文件myModule.js示例代码如下:
function testMethod1(numberValue) {
return numberValue + 10;
}
exports.testMethod1Export = testMethod1;
WXML示例代码如下:
<view>{{value1}}</view>
JS示例代码如下:
var myModule = require('./myModule');
Page({
data: {
value1: myModule.testMethod1Export(2)
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
创建名称为moduleExports_more的小程序项目,实现使用module.exports导出多个函数的写法。
模块文件myModule.js示例代码如下:
function testMethod1(numberValue) {
return numberValue + 10;
}
function testMethod2(numberValue) {
return numberValue + 100;
}
module.exports = { testMethod1, testMethod2 };
WXML示例代码如下:
<view>{{value1}}</view>
<view>{{value2}}</view>
JS示例代码如下:
var myModule = require('./myModule');
Page({
data: {
value1: myModule.testMethod1(3),
value2: myModule.testMethod2(3)
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
创建名称为exports_more的小程序项目,实现使用exports导出多个函数的写法。
模块文件myModule.js示例代码如下:
function testMethod1(numberValue) {
return numberValue + 10;
}
function testMethod2(numberValue) {
return numberValue + 100;
}
exports.testMethod1Export = testMethod1;
exports.testMethod2Export = testMethod2;
WXML示例代码如下:
<view>{{value1}}</view>
<view>{{value2}}</view>
JS示例代码如下:
var myModule = require('./myModule');
Page({
data: {
value1: myModule.testMethod1Export(4),
value2: myModule.testMethod2Export(4)
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.4.4 脚本的执行顺序
在浏览器中,脚本严格按照加载的顺序执行。
创建名称为runOrder的文件夹。
文件a.js示例代码如下:
console.log('a.js');
文件b.js示例代码如下:
console.log('b.js');
文件runOrder.html示例代码如下:
<html>
<head>
<!--a.js-->
<!--console.log('a.js')-->
<script src="a.js"></script>
<script>
console.log('inline script')
</script>
<!--b.js-->
<!--console.log('b.js')-->
<script src="b.js"></script>
</head>
</html>
程序运行效果如图2-xx所示。
图2-xx 运行效果
而在小程序中,脚本的执行顺序会有所不同。小程序执行的入口文件是app.js。并且会根据其中require模块的加载顺序决定文件的运行顺序。
创建名称为requireRunOrder的小程序项目。
文件a.js示例代码如下:
console.log('a.js');
文件b.js示例代码如下:
console.log('b.js');
文件app.js示例代码如下:
/* a.js
console.log('a.js')
*/
var a = require('./a')
console.log('app.js')
/* b.js
console.log('b.js')
*/
var b = require('./b')
程序运行效果如图2-xx所示。
图2-xx 运行效果
当app.js执行结束后,小程序会按照开发者在app.json中定义pages的顺序逐一执行。
创建名称为pageRunOrder的小程序项目。
文件app.json示例代码如下:
{
"pages": [
"pages/index/index",
"pages/logs/logs",
"pages/result/result"
],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "Weixin",
"navigationBarBackgroundColor": "#ffffff"
},
"style": "v2",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json"
}
在app.json文件中删除了如下配置:
"lazyCodeLoading": "requiredComponents"
文件app.js示例代码如下:
console.log('app.js');
App({
})
文件pages/index/index.js示例代码如下:
console.log('pages/index/index');
Page({
})
文件page/log/log.js示例代码如下:
console.log('pages/log/log');
Page({
})
文件page/result/result.js示例代码如下:
console.log('pages/result/result');
Page({
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
2.2.4.5 作用域
同浏览器中运行的脚本文件有所不同,小程序脚本的作用域同NodeJS更为相似。
在文件中声明的变量和函数只在该文件中有效,不同文件中可以声明相同名字的变量和函数,并不会互相影响,下面测试一下。
创建名称为ownLocalVar的小程序项目。
文件a.js代码如下:
// 定义局部变量
var localValue = 'a';
console.log('a.js 中:' + localValue); // 输出:a.js 中:a
文件b.js代码如下:
// 尝试访问a.js中的局部变量
console.log('b.js 中:' + localValue);
// 会报错: ReferenceError: localValue is not defined
文件index.js代码如下:
require('./a.js');
require('./b.js');
Page({
onLoad() {
console.log('页面加载完成');
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
当要使用全局变量时,需要使用全局函数getApp()获取全局的实例并设置相关的属性值来达到设置全局变量的目的。下面来测试一下。
创建名称为globalVar的小程序项目。
文件a.js代码如下:
// 获取全局变量
var global = getApp();
// 设置相关属性值
global.globalValue = 'globalValue';
文件b.js代码如下:
// 访问全局变量
var global = getApp();
// 输出 globalValue
console.log("b.js print result:" + global.globalValue);
文件index.js代码如下:
require('./a.js');
require('./b.js');
Page({
onLoad() {
console.log('页面加载完成');
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
需要注意的是,上面示例只有在a.js比b.js先执行才有效,否则呢?测试一下吧。
创建名称为bBefore_aAfter的小程序项目。
更改index.js代码如下:
require('./b.js');
require('./a.js');
Page({
onLoad() {
console.log('页面加载完成');
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
如果需要保证全局数据可以在任何文件中被安全正确的使用到,那么可以在App()中进行设置。
创建名称为globalVarOK的小程序项目。
文件app.js代码如下:
App({
globalData: 1
})
文件a.js代码如下:
// 局部变量
var localValue = 'a';
// 获取global变量
var app = getApp();
// 修改global变量
app.globalData++;// 执行后globalData数值为2
文件b.js代码如下:
// 定义另外的局部变量,并不会影响a.js中文件变量
var localValue = 'b';
// 如果先执行了a.js这里的输出应该是2
console.log(getApp().globalData);
文件index.js代码如下:
require('./a.js');
require('./b.js');
Page({
onLoad() {
console.log('页面加载完成');
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
如果先加载b.js,则打印全局变量globalData的结果为1。
创建名称为globalDataPrint_1的小程序项目。
更改index.js代码如下:
require('./b.js');
require('./a.js');
Page({
onLoad() {
console.log('页面加载完成');
}
})
程序运行效果如图2-xx所示。
图2-xx 运行效果
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。