从前面的学习我们了解到,函数可以操作(增删改查)数据(包括字符串、数组、对象、Boolean等所有数据类型),组件拥有了属性数据,也就拥有了被编程的能力,可见携带数据的重要性(id、class、style甚至点击事件都是组件携带的数据,都可以用来编程)。这一节我们就拿深入了解,组件是如何携带数据的,事件对象数据的作用以及数据如何跨页面渲染。
链接携带数据
URL链接的特殊字符
在日常生活中,我们经常可以看到有的链接特别长,比如百度、京东、淘宝等搜索某个关键词的链接,下面是使用百度搜索云开发时的链接:
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=云开发&rsv_pq=81ee270400007011&rsv_t=ed834wm24xdJRGRsfv7bxPKX%2FXGlLt6fqh%2BiB9x5g0EUQjyxdCDbTXHbSFE&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_sug3=20&rsv_sug1=19&rsv_sug7=100&rsv_sug2=0&inputT=5035&rsv_sug4=6227
以及之前在视频组件里用到的视频链接:
http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400&bizid=1023&hy=SH&fileparam=302c020101042530230204136ffd93020457e3c4ff02024ef202031e8d7f02030f42400204045a320a0201000400
这些链接通常包括以下特殊字符,以及都有着基本相同的含义,通过这些特殊字符,链接就被塞进了很多数据信息,其中?、&、=是我们接下来关注的重点。
- / 分隔目录和子目录
- ? 分隔实际的URL和参数
- & URL中指定的参数间的分隔符
- = URL中指定的参数的值
-
同一个页面的位置标识符,类似于页面的书签;
获取URL链接的数据
使用开发者工具,新建一个lifecyle的页面,以及在home页面新建一个二级页面detail(也就是在pages配置项新建一个pages/home/detail/detail,以及注意将lifecycle设置为首页)然后在lifecyle.wxml里输入以下代码,这里的url也通过?、&、=添加了很多数据:
<navigator id="detailshow" url="./../home/detail/detail?id=lesson&uid=tcb&key=tap&ENV=weapp&frompage=lifecycle" class="item-link">点击链接看控制台</navigator>
点击链接,发现页面仍然能够跳转到detail页面,给url所添加的数据并不会改变页面的路径,毕竟页面的路径通常是由/来控制的。
那链接携带的数据的作用是什么呢?大家发现没,本来点击的是lifecycle里的链接,但是却跳转到了detail,如果链接携带的数据一直都在,只要我们可以在detail里把链接的数据给获取到,那我们是不是实现了数据的跨页面呢?
获取url参数的生命周期函数onload
onload是Page页面的生命周期函数,当页面加载时触发。一个页面只会调用一次,可以在 onLoad 函数的参数中获取打开当前页面路径中的参数。
使用开发者工具,在detail.js的onload函数里添加console.log,把onload函数的参数打印出来:
onLoad: function (options) {
console.log(options)
},
再次点击lifecycle.wxml页面的链接,会跳转到detail,页面加载时会触发生命周期回调函数onload,会打印函数里的参数options,我们可以看看控制台的打印信息。
{id: "lesson", uid: "tcb", key: "tap", ENV: "weapp", frompage: "lifecycle"}
相信大家会这样的数据类型非常熟悉,它就是一个对象Object,我们可以通过点表示法,获取到对象里具体的属性,比如options.id就能显示我们在lifecycle点击的组件的id。
数据跨页面
回到之前列表渲染章节的电影列表页面(你可以把之前关于电影列表的wxml和wxsss以及数据代码复制粘贴到lifecycle),给Navigator组件添加一些信息,找到下面的代码:
<navigator url="" class="weui-media-box weui-media-box_appmsg" hover-class="weui-cell_active">
将其修改为如下,也就是添加id={{index}},将每部电影的id、name、img、desc等信息写进链接
<navigator url="./../home/detail/detail?id={{index}}&name={{movies.name}}&img={{movies.img}}&desc={{movies.desc}}" class="weui-media-box weui-media-box_appmsg" hover-class="weui-cell_active">
编译之后,在lifecycle页面点击其中一部电影,我们发现所有链接还是会跳转到detail,但是控制台输出的信息却不一样,点击哪一部电影,就会在控制台输出哪部电影的信息,数据不仅实现了跨页面,还实现了点哪个显示哪个的区分。
跨页面数据渲染
当然我们也可以继续把数据使用setData渲染到detail页面,为方便我们仅渲染图片信息,在detail.wxml里输入:
<image mode="widthFix" src="{{detail.img}}" sytle="height:auto"></image>
在detail.js的data里添加一个detail对象,detail对象三个属性用来接收setData的数据,所以可以为空值:
detail:{
name:"",
img:"",
desc:""
},
然后在onload生命周期函数里将options的值赋值给detail
onLoad: function (options) {
console.log(options)
this.setData({
detail: options,
}
)
},
这样,我们在lifecycle里点击哪部电影,哪部电影的海报就在detai页里显示啦。
不过使用链接url传递参数有字节限制以及只能在跨页面中使用,但是可以用来传递比如页面链接来源,可以追踪用户来自于什么设备、什么App、通过什么方式以及来自哪个朋友的邀请链接;还可以用于一些网页链接的API必备的id、key等。跨多个页面以及传递更多参数、数据等,可以使用公共数据存储app.globalData(本节会介绍)、数据缓存(后面章节会介绍)、数据库(云开发部分会介绍)以及新增的页面间通信接口getOpenerEventChannel(这里不多介绍)
组件携带数据dataset
组件有公有属性和私有属性,这些属性都是数据,事件处理函数可以修改这些属性,从而让组件有丰富的表现形式。不仅如此,在组件节点中还可以附加一些自定义数据。在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理,从而让组件变成相当复杂且强大的编程对象。
使用JavaScript代替Navigator组件
使用开发者工具在lifecycle.wxml里输入以下代码,
<image id="imageclick" src="https://img13.360buyimg.com/n7/jfs/t1/842/9/3723/77573/5b997bedE4f438e5b/ccd1077b985c7150.jpg" mode="widthFix" style="width:200rpx" bindtap="clickImage"></image>
然后我们在lifecycle.js里添加如下代码,在上一节我们说过当点击组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象,我们仍然把这个事件对象打印出来:
clickImage:function(event){
console.log('我是button',event)
wx.navigateTo({
url: "/pages/home/detail/detail?id=imageclick&uid=tcb&key=tap&ENV=weapp&frompage=lifecycle"
})
},
当我们点击lifecycle页面的图片时,clickImage会收到一个事件对象,打印出来的结果里包含着target和currentTarget两个属性,currentTarget指向事件所绑定的元素,而target始终指向事件发生时的元素。由于这个案例事件绑定的元素和事件发生时的元素都是imageclick,所以它们的值相同,它们里面都包含了当前组件的id,以及dataset,那这个dataset是啥呢?
值得强调的是很多童鞋以为只有点击Navigator组件、button组件才能进行链接跳转,这是思维定势的误区,通过bindtap,组件被赋予了一定的编程能力,尽管没有url属性,使用wx.navigateTo也能具备这种能力。
自定义属性dataset
我们给上面的image加一个父级组件,这里的data-sku、data-spu和data-pid的值以及图片使用的都是京东iphone的数据。这些自定义数据以 data- 开头,多个单词由连字符 – 连接。
<view id="viewclick" style="background-color: red;padding:20px;" data-sku="100000177760" data-spu="100000177756" data-pid="100000177756" data-toggle="Apple iPhone XR" data-jd-color="Red" data-productBrand="Apple" bindtap="clickView">
<image id="imageclick" src="https://img13.360buyimg.com/n7/jfs/t1/842/9/3723/77573/5b997bedE4f438e5b/ccd1077b985c7150.jpg" mode="widthFix" style="width:200rpx" bindtap="clickImage">点击button</image>
</view>
然后再在lifecycle.js里添加事件处理函数clickView,
clickView: function (event) {
console.log('我是view',event)
wx.navigateTo({
url:"/pages/home/detail/detail?id=viewclick&uid=tcb&key=tap&ENV=weapp&frompage=lifecycle"
})
},
当我们点击红色空白处(非图片区域)时,只会触发clickView,target与currentTarget的值相同。而当我们点击图片时,就会触发两个事件处理函数。
我们点击的是图片image组件,却分别触发了绑定在image组件以及image的父级(上一级)组件view的事件处理函数,我们称这为事件冒泡。
注意这时clickView事件对象的currentTarget和target的值就不相同了。在点击图片的情况下只有在clickView事件对象的currentTarget里看到dataset获取到了view组件的自定义数据。
同时从detail页面的打印(注意两个事件的链接有id的值不同)可以看出,点击图片,跳转到的是图片绑定的事件指定的页面,页面的id为imageclick。
我们再来观察dataset的值,发现jdColor以及productbrand,这是因为dataset会把连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。data-jd-color变成了jdColor,而data-productBrand转成了productbrand。也就是说我们点击组件,从事件对象的dataset里,我们可以通过event.currentTarget.dataset来获取组件的自定义数据。
点击组件显示当前组件其他数据
通过事件对象我们不仅可以明确知道点击了什么组件,而且还可以获取当前组件的自定义数据。比如上面案例中我们可以轻松获取到京东该商品的pid,从而跳转到该商品的详情页(https://item.jd.com/京东商品的pid.html),我们可以在clickView事件处理函数里添加:
let jdpid=event.currentTarget.dataset.pid
let pidurl = "https://item.jd.com/" + jdpid + ".html"
console.log(pidurl)
这样链接该商品的详情页就被打印出来啦~(小程序不支持navigateTo的外链跳转)。如果我们要获取当前组件的其他相关数据,使用事件对象非常方便,比如点击小图显示大图,toggle弹出其他内容等等。
小程序也支持给data-属性添加wxss样式,比如我们可以给data-pid添加样式, view[data-pid]{margin:30px;},data-属性既可以类似于选择器一样的存在,也可以对它进行编程,是不是很强大?
小程序和页面的生命周期
App()函数注册小程序,Page()函数注册小程序中的一个页面,他们都接受的是对象Object类型的参数,包含一些生命周期函数和事件处理函数。App() 必须在 app.js 中调用,必须调用且只能调用一次。开发者可以添加任意的函数或数据变量到 Object参数中,用 this 可以访问。
小程序构造器:App(Object object)
页面构造器:Page(Object object)
小任务:为什么我们不能在App()和Page()里不能直接用等号=给变量赋值?你明白了吗?注意函数语句与对象属性与方法在写法上的不同。
打印日志了解生命周期
对小程序和页面的生命周期,我们可以通过打印日志的方式来了解生命周期函数具体的执行顺序和情况,使用开发者工具在app.js里给onLaunch、onShow、onHide添加一些打印日志。
onLaunch(opts) {
console.log('onLaunch监听小程序初始化。',opts)
},
onShow(opts) {
console.log('onShow监听小程序启动或切前台',opts)
},
onHide() {
console.log('onHide监听小程序切后台')
},
想必大家已经注意,有的参数写的options,有的写的却是opts;前面事件对象有的写的是event,有的则用的是e,这个参数都是可以自定义的哦
以及在lifecylce.js的js里添加
onLoad: function(options) {
console.log("onLoad监听页面加载",options)
},
onReady: function() {
console.log("onReady监听页面初次渲染完成")
},
onShow: function() {
console.log("onShow监听页面显示")
},
onHide: function() {
console.log("onHide监听页面隐藏")
},
onUnload: function() {
console.log("onUnload监听页面卸载")
},
通过在模拟器执行各种动作,比如编译、点击转发按钮、点击小程序转发按钮旁的关闭按钮(并没有关闭)、页面切换等来了解生命周期函数的执行顺序(比如页面生命周期),对切前台和切后台、页面的加载、渲染、显示、隐藏、卸载有一定的了解。
前面我们已经了解到,通过点击事件可以触发事件处理函数,也就是需要用户来点击某个组件才能触发;这里页面的生命周期函数也可以触发事件处理函数,它不需要用户点击组件,只需要用户打开小程序、打开某个页面,把小程序切后台等情况时就能触发里面的函数。
小程序打开场景值
在 App
的 onLaunch
和 onShow
打印的对象里有一个scene为1001,这个是场景值。场景值用来描述用户进入小程序的路径方式。用户进入你的小程序的方式有很多,比如有的是扫描二维码、有的是长按图片识别二维码,有的是通过微信群进入的小程序,有的是朋友单聊进入的小程序,有的是通过公众号进入的小程序等等,这些就是场景值,而具体的场景值,可以看技术文档,场景值对产品、运营来说非常重要。
技术文档:场景值列表
onLaunch与onShow
onLaunch是监听小程序的初始化,初始化完成时触发,全局只会触发一次,所以在这里我们可以用来执行获取用户登录信息的函数等一些非常核心的数据,如果onLaunch的函数过多,会影响小程序的启动速度。
onShow是在小程序启动,或从后台进入前台显示时触发,也就是它会触发很多次,在这里就不大适合放获取用户登录信息的函数啦。这两者的区别要注意。
用户登录与信息获取
小程序用户登录和获取用户信息相对来说比较复杂,为了能够让大家可以更加直观的结合我们之前的知识来一步步探究到底是怎么一回事,建议大家重新建一个不使用云服务的小程序项目。
了解wx.login、wx.getSetting
使用开发者工具将app.js的代码修改为如下(可以把之前的全部删掉或注释掉,把下面代码复制粘贴过去)。了解一个函数一个API,实战方面从打印日志开始,而理论方面从技术文档开始。
App({
onLaunch: function () {
wx.login({
success(res){
console.log('wx.login得到的数据',res)
}
})
wx.getSetting({
success(res){
console.log('wx.getSetting得到的数据',res)
}
})
},
globalData: {
userInfo: null
}
})
模板小程序用的是箭头函数的写法,大家可以结合之前关于箭头函数的介绍、模板小程序的代码和上面的写法对照来学习。
从控制台可以看到wx.login会得到errMsg和code,这个code是用户的登录凭证。而wx.getSetting则会得到errMsg和用户当前的权限设置authSetting,包含是否允许获取用户信息,是否允许获取用户位置,是否允许使用手机相册等权限。我们可以根据打印的结果结合技术文档来深入理解。
技术文档:获取用户登录凭证wx.login、获取用户当前权限设置wx.getSetting
如果要让小程序和自己的服务器账号打通,仅仅获取用户登录凭证是不够的,需要将这个code以及你的小程序appid和appSecret传回到你的开发服务器,然后在自己的服务器上调用auth.code2session接口,得到用户的openid和session_key。由于openid是当前用户的唯一标识,可以用来判断该用户是否已经在自己的服务器上注册过,如果注册过,则根据openid生成自定义登录态并返回给小程序,整个过程非常复杂。而由于云开发与微信登录鉴权无缝整合,这些内容都不会涉及,所以这里不多介绍。
获取用户信息wx.getUserInfo
我们要获取用户信息,首先需要判断用户是否允许,可以从authSetting对象里看scope.userInfo属性是否为true,如果为true,那我们可以调用wx.getUserInfo()接口来获取用户信息。
使用开发者工具给上面的wx.getSetting()函数添加内容,最终代码如下:
wx.getSetting({
success(res){
console.log('wx.getSetting得到的数据',res)
if (res.authSetting["scope.userInfo"]){
wx.getUserInfo({
success(res){
console.log("wx.getUserInfo得到的数据",res)
}
})
}
}
})
由于scope.userInfo是一个属性名,无法使用点表示法res.authSetting.scope.userInfo来获取到它的值(会误认为是authSetting属性下的scope属性的usrInfo属性值),这里用到的是获取对象属性的另外一种表示方法,叫括号表示法,也就是用中括号[]围住属性名,属性名需用单引号或双引号围住。
在控制台console我们可以看到userInfo对象里包含着当前登录用户的昵称、头像、性别等信息。
globalData
但是这个数据是在app.js里,和我们之前接触到的数据都在页面的js文件里有不同。而且这个用户信息的数据是所有页面都通用的,放在app.js里公用是应该的,但是我们要怎么才能调用到这个数据呢?
globalData对象通常用来存放整个小程序都会使用到的数据,比如我们可以把用户信息赋值给globalData的任意自定义属性。模板小程序已经声明了一个userInfo属性,我们也可以自定义其他属性名,比如(后面我们会用到)
tcbData:{
title:"云开发训练营",
year:2019,
company:"腾讯Tencent"
},
在上面的wx.getUserInfo的success回调函数里将获取到的userInfo对象赋值给globalData对象的userInfo属性。
wx.getUserInfo({
success(res){
console.log("wx.getUserInfo得到的数据",res)
this.globalData.userInfo = res.userInfo
}
})
但是会提示 Cannot read property 'globalData' of undefined;报错,但是模板小程序也是这样写代码的为什么却没有报错?这是因为箭头函数的this与非箭头函数this指向有不同。
that与this
this的指向情况非常复杂,尽管哪个对象调用函数,函数里面的this就指向哪个对象,说起来非常简单,但是场景太多,大家在开发时不必强行理解,死记硬背,把this打印出来即可。我们可以将回调函数success的this打印出来,
success(res){
console.log('this是啥',this)
}
结果是this undefined,并没有定义,和我们预计的是Page()函数对象并不一致,给它的this.globalData赋值当然会报错。
解决方法有两种,一种是模板小程序使用箭头函数,箭头函数继承的是外部对象的this,我们可以把代码wx.getSetting()里的success回调函数的写法都改为箭头函数的写法(这里有两个,只改一个行不行?试试看),这时我们可以再来打印this,看看是什么情况。
在控制台我们可以看到改为箭头函数之后的this的结果为一个pe对象,里面包含着Page()对象的生命周期函数和属性。
第二种方法是使用that指代,在wx.getSetting()函数的前面写一行代码:
let that=this
wx.getSetting({............}) //为了便于你找位置
然后把wx.getUserInfo的success回调函数的改为如下:
wx.getUserInfo({
success: res =>{
console.log('that是啥',that)
console.log("wx.getUserInfo得到的数据",res)
that.globalData.userInfo = res.userInfo
}
})
由于情况复杂,this的指向经常会变,但是在this的指向还是Page()对象时,我们就把this赋值给that,这样就不会因为this指向变更而出现undefined了。
getApp()
那我们如何在页面的js里调用globalData呢,这个时候就需要用到getApp()函数啦。
技术文档:getApp()
使用开发者工具新建一个user页面,然后在user.js的Page()函数前面添加如下代码:
let app = getApp()
console.log('user页面打印的app', app)
console.log('user页面打印的globalData', app.globalData.userInfo)
console.log('user页面打印的tcbData',app.tcbData.eventInfo)
这样我们就能获取app.js里的globalData和自定义的属性了。
这里还会有一个问题,就是尽管我们已经获取到了globalData,我们也能在globalData.userInfo的打印日志里看到用户的信息,但是当我们想获取里面的值时,还是会报错,这是因为 wx.getUserInfo是异步获取的信息,这里涉及到的异步,我们之后会详细介绍。
通过button来获取用户信息
在我们使用wx.getUserInfo的方式来获取用户信息时,控制台会报错: 获取 wx.getUserInfo 接口后续将不再出现授权弹窗,请注意升级。
也就是小程序官方已经不建议开发者用wx.getUserInfo来获取用户信息了,而是建议通过button的方式来获取,对用户的体验更好,也就是用户只有点击了按钮,用户信息才会被获取。
使用开发者工具在user.wxml里输入以下代码,这是一个button组件,要获取到用户信息,有两个必备条件,一是 open-type="getUserInfo",必须是这个值;二是绑定事件处理函数的属性名为bindgetuserinfo(类似于bindtap,但是属性名必须为bindgetuserinfo,至于事件处理函数的名称可以自定义)
<button open-type="getUserInfo" bindgetuserinfo="getUserInfomation"> 点击获取用户信息 </button>
这里的getUserInfomation和之前点击事件的事件处理函数是一致的,点击组件触发getUserInfomation,仍然会收到事件对象,我们把它打印出来,在user.js里添加以下代码:
getUserInfomation: function (event) {
console.log('getUserInfomation打印的事件对象',event)
},
当我们点击“点击获取用户信息”的button按钮后,在控制台可以查看到getUserInfomation打印的事件对象,事件对象里有个detail属性,里面就有userInfo的数据,这个具体如何调用,详细大家结合之前学过的知识应该有所了解。
首先在user.js的data里初始化一个userInfo对象,用来接收数据:
data: {
userInfo:{}
},
然后在事件处理函数getUserInfomation获取到的userInfo通过this.setData赋值给它,也就是getUserInfomation的函数为
getUserInfomation: function (event) {
console.log('getUserInfomation打印的事件对象',event)
this.setData({
userInfo: event.detail.userInfo,
})
},
这时data里的userInfo就有用户信息了,我们可以在user.wxml添加以下代码来将数据渲染出来。
<view>{{userInfo.nickName}}</view>
<view>{{userInfo.country}}</view>
<image mode="widthFix" style="width:64px;height:64px" src="{{userInfo.avatarUrl}}"></image>
当我们再次点击“点击获取用户信息”的button按钮后,数据就渲染出来了。
将用户信息写进app.js
这种方式只能在user页面才能获取到用户信息,限制非常大,那我们应该怎么做呢?我们要把获取到的用户信息写到app.js成为页面的公共信息,以后可以跨页面只需在user页面点击一次按钮即可。
在getUserInfomation将获取到的用户信息传给globalData的userInfo属性:
getUserInfomation: function (event) {
console.log('getUserInfomation打印的事件对象',event)
app.globalData.userInfo = event.detail.userInfo
this.setData({
userInfo: event.detail.userInfo,
})
},
关于用户登录以及信息获取,这里我们只是梳理了一些比较核心的知识点,还有一些大家可以去参考模板小程序里的代码,这里有一套相对比较完整的案例。更具有实际开发意义的用户登录,之后会在云开发部分介绍。
获取用户信息还有一种方式,就是通过组件<open-data&来展示,由于比较简单,这里就不多介绍啦。