微信小程序(三)数据绑定&列表渲染&事件绑定&导航栏&常用API&生命周期&模板&组件
这里新建个页面log,然后用这个页面进行测试。 同时修改app.json,将log 页面设置为首页
"pages": [
"pages/index/index",
"pages/log/log"
],
"entryPagePath": "pages/log/log",
0. 数据绑定
0. 简单的绑定
wxml 用 {{val}} 取变量
<!--pages/log/log.wxml-->
<text>pages/log/log.wxml</text>
<view>{{data1}}</view>
<!--数据绑定-->
<view>
<!--单项绑定-->
<input type="text" placeholder="username1" value="{{username1}}"/>
<!--双项绑定-->
<input type="text" placeholder="username2" model:value="{{username2}}"/>
</view>
js 里面的data:
data: {
data1: '测试1',
username1: '张三',
username2: '张三'
},
简单理解:
(1). 单项绑定: 数据只能从 data -> view。
(2). 双向绑定: 数据能从view -> data。 也就是页面修改元素值,对应data 的值,也能改变。这种一般用于表单元素。
例子:
补充:
1. <input model:value="{{ a.b }}" /> 这样的表达式目前暂不支持。
2. 可以自己实现:
wxml:
<van-field
model:value="{{ task.content }}"
data-gater="task.content"
bindinput="inputFrame"
label="任务: "
type="textarea"
placeholder="请输入任务"
autosize
border="{{ false }}"
/>
js: inputFrame
inputFrame(e) {
this.setData({[`${e.currentTarget.dataset.gater}`]: e.detail});
}
1. 数据修改&js获取
Js 中修改:
this.setData({username1: '张三2', 'newKey': '1122'});
这个是字段覆盖,如果没有字段会新增字段。 不会影响其他的key。
js中获取:
this.data.key1...
2. 数据劫持原理
通过Object.defineproperty 进行劫持,get、set 方法。
<script type="text/javascript">
let data = {
"username": "张三",
"age": 25
}
// 模拟组件对象
let _this = {
"sex": "男"
}
// 数据劫持
for (let item in data) {
Object.defineProperty(_this, item, {
// 用来获取扩展属性值, 获取_this 的这个属性,一定需要调用get 方法
get() {
console.log('get', item)
return data[item];
},
set(newValue) {
console.log('set', item);
data[item] = newValue;
}
})
}
console.log(_this)
_this.username = "李四";
_this.sex = "女";
console.log("======")
console.log(_this)
</script>
控制台打印:
可以看到通过劫持的属性不会直接打印,劫持的属性点击后面的... 获取属性的时候才会调用到 get 方法。
修改属性会调用到set,set 里面需要自己修改原data数据,这样才能和get 对应上。
3. 和vue 数据绑定区别
vue:
双向绑定:v-model 数据可以从data流向页面,也可以从页面流向data。一般应用在表单类元素上(input,select...)
单向绑定: v-bind 单向绑定只能从data修改页面中的值,不能从页面修改data的值。
双向绑定原理:
vue 通过数据劫持的方式实现双向绑定。当一个vue 实例被创建时,vue 会对其数据对象进行递归遍历,将每个属性转换为getter/setter,并且在内部维护一个依赖列表。当数据发生变化时,vue 会通知依赖列表的所有watcher 对象,让他们更新视图。
4. 绑定到this 对象上,和页面生命周期一样,单例
onLoad(options) {
this.customKey = {"username":"zs", "age": 25};
console.log(this)
},
结果: 和data 同级,使用的时候可以 this.customKey.age...
1. 列表渲染
if、for 语句简单使用。 对于for 循环,一般需要用wx:key="unique" 为多个相同的元素指定唯一的key。
wxml
<!--pages/log/log.wxml-->
<text>pages/log/log.wxml</text>
<!--if-->
<view wx-if="{{data1}}">data1: {{data1}}</view>
<!--if...else-->
<view wx-if="{{data2}}">data2: {{data2}}</view>
<view wx:else>data2:为空</view>
<!--
for 循环,默认是下标是index、元素是item。
可以使用 wx:for-item 可以指定数组当前元素的变量名,使用 wx:for-index 可以指定数组当前下标的变量名:
-->
<view wx:for="{{messages}}">
{{index}}: {{item.message}}
</view>
Data:
/**
* 页面的初始数据
*/
data: {
data1: '测试1',
data2: '',
messages: [{
message: 'foo',
}, {
message: 'bar'
}]
},
结果:
hidden 用于隐藏元素。hidden 使用, 只能对块级元素生效,对 flex 布局的元素不生效。 如果想生效,可以display 属性自己隐藏。
<view hidden="{{hiddenView}}">动态显示与隐藏</view>
2. 事件绑定
下面有几个注意点:
1. bind 的事件会冒泡, catch会阻止冒泡
2. 当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
属性 类型 说明
type String 事件类型
timeStamp Integer 事件生成时的时间戳
target Object 触发事件的组件的一些属性值集合
currentTarget Object 当前组件的一些属性值集合
mark Object 事件标记数据
3. Event.target 和 event.currentTarget 的区别
a) Event.target 是触发事件的对象,但不一样是绑定事件的对象,如: 事件委托,冒泡
b) currentTarget 触发时间的对象一定是绑定事件的对象, 没有事件委托
4. 参数绑定
需要在wxml 对应标签用 data-key="value" 的形式进行绑定, 获取的时候用 event.target.dataset.key || event.currentTarget.dataset.key
xml:
<!--pages/log/log.wxml-->
<text>pages/log/log.wxml</text>
<button bindtap="handleTap">
Click here!
</button>
<!--绑定事件为动态的函数名称-->
<button catchtap="{{handlerName}}">
Click here2!
</button>
<!--绑定事件为动态的函数名称,且传递参数-->
<button catchtap="{{handlerName}}" data-name="张三" data-age="20">
Click here3!
</button>
Js
// pages/log/log.js
Page({
/**
* 页面的初始数据
*/
data: {
handlerName: 'handleTap'
},
/** S 自定义函数 */
handleTap: (e) => {
console.log(e);
wx.showModal({
title: '提示',
content: '这是一个模态弹窗',
success (res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
}
/** E 自定义函数 */
})
依次点击,查看1、2、3,查看输出:
3. 导航栏
修改app.json 文件,设置导航栏。 例如:
entryPagePath 是默认的首页,如果不设置entryPagePath ,默认是pages 数组的第一个。
{
"pages": [
"pages/index/index",
"pages/log/log",
"pages/log2/log2"
],
"entryPagePath": "pages/log/log",
"window": {
"navigationBarBackgroundColor": "#ddd",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "Geogre"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页"
},{
"pagePath": "pages/log/log",
"text": "日志"
},{
"pagePath": "pages/log2/log2",
"text": "日志2"
}]
}
}
另外:iconPath、selectedIconPath 可以设置未激活和激活状态的图标。
效果:
4. 常用API
小程序提供了很多实用的方法,都存在全局对象wx 对象中。
1. 路由跳转
在小程序中所有页面的路由全部由框架进行管理。框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,页面栈的表现如下:
开发者可以使用 getCurrentPages()
函数获取当前页面栈。
路由跳转:
-- 页面切换. (url 不能是app.json的tabBar 配置的导航页面)
wx.navigateTo(Object object):保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。 也就是上面菜单上有后退按钮,可以返回上个页面。
wx.redirectTo(Object object):关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。不支持后退,只能回到主页面。
-- 切换tab (url 必须是app.json的tabBar 配置的导航页面)
wx.switchTab(Object object):跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
-- 任意切换
reLaunch 关闭所有页面,打开到应用内的某个页面
总结:
navigateTo, redirectTo 只能打开非 tabBar 页面。
switchTab 只能打开 tabBar 页面。
reLaunch 可以打开任意页面。
页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
调用页面路由带的参数可以在目标页面的onLoad中获取。
比如:
handleTap: (e) => {
wx.navigateTo({
url: '/pages/log2/log2',
})
},
handleTap2: (e) => {
wx.switchTab({
// 切换导航,需要在app.json的tabBar 配置导航页面
url: '/pages/index/index',
})
}
也可以用navigator 进行跳转
<navigator url="/pages/index/index" open-type="reLaunch" class="navigator">
<button>跳转到首页</button>
</navigator>
携带参数: 在路径上加参数,但是注意参数超过一定长度会被截断,所以一般传ID参数。 还可以用本地存储共享变量的方式。
wx.navigateTo({
url: '/pages/log3/log3?key1=value1&key2=value2',
})
log3 的onLoad 方法的options 就是参数(是一个对象)
onLoad(options) {
console.log(options)
},
// 结果
{key1: "value1", key2: "value2"}
通过appData 选项卡,也可以看到当前存活的页面,可以知道原来页面是销毁还是隐藏:
2. 界面交互
a) 显示消息提示框: wx.showToast()
b) 显示消息加载框: wx.showLoading(), 必须显示的调用hideLoading() 才能隐藏
c) 关闭消息提示框: wx.hideToast()
d) 关闭消息加载框: wx.hideLoading()
e) 模态框:wx.showModal(Object object)
例如:
/** S 自定义函数 */
handleTap: (e) => {
wx.showToast({
title: '成功',
icon: 'success',
duration: 2000
})
},
handleTap2: (e) => {
wx.showLoading({
title: '加载中',
})
setTimeout(function () {
wx.hideLoading()
}, 2000)
},
handleTap3: (e) => {
wx.showModal({
title: '提示',
content: '这是一个模态弹窗',
success (res) {
if (res.confirm) {
console.log('用户点击确定')
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
}
/** E 自定义函数 */
3. 网络请求
- 语法: wx.request()
- 注意点:
- 协议必须是https协议
- 一个接口最多配置20个域名
- 并发限制上限是10个
- 开发过程中设置不校验合法域名: 开发工具 ---> 右上角详情 ----> 本地设置 ---> 不校验
例如:
handleTap3: (e) => {
wx.request({
url: 'http://localhost:8090/template/list',
data: {
x: '',
y: ''
},
method: 'GET', // 默认值
dataType: 'json', // 对返回值调用JSON.parse(), 默认值
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
console.log(res.data)
}
})
}
- 下载文件
wx.downloadFile(Object object)
- 上传文件
wx.uploadFile(Object object): 将本地资源上传到服务器。客户端发起一个 HTTPS POST 请求,其中 content-type 为 multipart/form-data。
- 其他
也有websocket,tcp,udp 相关接口。
4. 本地存储
- 语法:
带sync 的是同步版本,不带的是异步获取,需要在成功回调方法编写相关逻辑
--- 设置
wx.setStorageSync(): 将数据存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容。除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
wx.setStorage(): 同上面,只不过是异步操作的。
-- 获取
wx.getStorageSync()
wx.getStorage()
-- 获取所有的
getStorageInfo | getStorageInfoSync。 返回值:
属性 类型 说明
keys Array.<string> 当前 storage 中所有的 key
currentSize number 当前占用的空间大小, 单位 KB
limitSize number 限制的空间大小,单位 KB
-- 删除
a) wx.removeStorage() 异步
b) wx.removeStroageSync() 同步
-- 清空
c) wx.clearStorage() 异步
d) wx.clearStorageSync() 同步
- 注意点:
1. 建议存储的数据为json数据
2. 单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB
3. 属于永久存储,同H5的localStorage一样
- 例如:
// pages/log/log.js
Page({
/** S 自定义函数 */
handleTap: (e) => {
wx.getStorageInfo({
success (res) {
console.log(res.keys)
console.log(res.currentSize)
console.log(res.limitSize)
}
})
const res = wx.getStorageInfoSync()
console.log("getStorageInfoSync", res.keys)
console.log("getStorageInfoSync", res.currentSize)
console.log("getStorageInfoSync", res.limitSize)
},
handleTap2: (e) => {
wx.setStorageSync('key1', 'value1')
// 开启加密存储
wx.setStorage({
key: "key2",
data: "value2",
encrypt: true, // 若开启加密存储,setStorage 和 getStorage 需要同时声明 encrypt 的值为 true
})
},
handleTap3: (e) => {
wx.getStorage({
key: "key2",
encrypt: true, // 若开启加密存储,setStorage 和 getStorage 需要同时声明 encrypt 的值为 true
success(res) {
console.log("getStorage", res.data)
}
})
var value = wx.getStorageSync('key1')
console.log("getStorageSync", value)
}
/** E 自定义函数 */
})
查看getStorageInfo 和 getStorageInfoSync返回控制台:
也可以在调试器的storage 板块查看本地存储:
5. 生命周期
下图说明了页面 Page
实例的生命周期。引用官网的图:
我们新建一个页面也可以看到默认生成的JS函数,如下:
// pages/log3/log3.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
这里需要注意:
一般数据的初始化写在:onLoad 方法内部;onShow、onHide 可能会调用多次(在路由使用navigateTo 跳转时会保留当前页面,后退会继续回到当前页面调用onShow 方法), onLoad和onUnLoad、onRead 只会调用一次。
6. 模板使用
- 模板wxml
pages目录新建template/template1.1xml
<template name="msgItem">
<view>
<view>{{index}}: {{msg}}</view>
<view>Time: {{time}}</view>
</view>
</template>
<template name="messages">
<view wx:for="{{messages}}">
{{index}}: {{item.message}}
</view>
</template>
- 页面引入
<import src="../template/template1"></import>
<!--pages/log/log.wxml-->
<text>pages/log/log.wxml</text>
<button bindtap="handleTap">
Click here!
</button>
<button bindtap="handleTap2">
Click here2!
</button>
<button bindtap="handleTap3">
Click here3!
</button>
<template is="msgItem" data="{{...item}}"></template>
<view>=====</view>
<template is="messages" data="{{messages}}"></template>
Js 数据:
data: {
item: {
index: 0,
msg: "测试消息",
time: "0709"
},
messages: [{
message: 'foo',
}, {
message: 'bar'
}]
},
结果:
7. 自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
注意先修改project.config.json里面setting 模块:
"ignoreDevUnusedFiles": false
1. 创建自定义组件
类似于页面,一个自定义组件由 json
wxml
wxss
js
4个文件组成。要编写一个自定义组件,首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可将这一组文件设为自定义组件):
{
"component": true
}
wxml
<!--pages/component/cus-table/cus-table.wxml-->
<text>pages/component/cus-table/cus-table.wxml</text>
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
</view>
<view class="inner">
{{innerLabel}}
</view>
<slot></slot>
wxss
/* pages/component/cus-table/cus-table.wxss */
/* 这里的样式只应用于这个自定义组件 */
.inner {
color: red;
}
js
// pages/component/cus-table/cus-table.js
Component({
/**
* 组件的属性列表
*/
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {},
innerLabel: "内部数据"
},
methods: {
// 这里是一个自定义方法
customMethod: function(){
}
}
})
2. 使用组件
- json
{
"usingComponents": {
"cus-table": "/pages/cus-component/cus-table/cus-table"
}
}
- wxml
<view>
<!-- 以下是对一个自定义组件的引用 -->
<cus-table inner-text="Some text"></cus-table>
</view>
自定义组件的 wxml
节点结构在与数据结合之后,将被插入到引用位置内。这里注意标签名称和传递的参数必须和组件声明时候对应上。
8. 参考:
小程序相关文档:https://developers.weixin.qq.com/miniprogram/dev/framework/
双向绑定:https://developers.weixin.qq.com/miniprogram/dev/framework/view/two-way-bindings.html
事件:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html
模板:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/template.html