从前面的学习我们了解到,函数可以操作(增删改查)数据(包括字符串、数组、对象、Boolean 等所有数据类型),组件拥有了属性数据,也就拥有了被编程的能力,可见携带数据的重要性(id、class、style 甚至点击事件都是组件携带的数据,都可以用来编程)。这一节我们就拿深入了解,组件是如何携带数据的,事件对象数据的作用以及数据如何跨页面渲染。
在日常生活中,我们经常可以看到有的链接特别长,比如百度、京东、淘宝等搜索某个关键词的链接,下面是使用百度搜索云开发时的链接:
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
这些链接通常包括以下特殊字符,以及都有着基本相同的含义,通过这些特殊字符,链接就被塞进了很多数据信息,其中?、&、=是我们接下来关注的重点。
使用开发者工具,新建一个 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 里把链接的数据给获取到,那我们是不是实现了数据的跨页面呢?
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(这里不多介绍)
组件有公有属性和私有属性,这些属性都是数据,事件处理函数可以修改这些属性,从而让组件有丰富的表现形式。不仅如此,在组件节点中还可以附加一些自定义数据。在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理,从而让组件变成相当复杂且强大的编程对象。
使用开发者工具在 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 也能具备这种能力。
我们给上面的 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(url)
这样链接该商品的详情页就被打印出来啦~(小程序不支持 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 是监听小程序的初始化,初始化完成时触发,全局只会触发一次,所以在这里我们可以用来执行获取用户登录信息的函数等一些非常核心的数据,如果 onLaunch 的函数过多,会影响小程序的启动速度。
onShow 是在小程序启动,或从后台进入前台显示时触发,也就是它会触发很多次,在这里就不大适合放获取用户登录信息的函数啦。这两者的区别要注意。
小程序用户登录和获取用户信息相对来说比较复杂,为了能够让大家可以更加直观的结合我们之前的知识来一步步探究到底是怎么一回事,建议大家重新建一个不使用云服务的小程序项目。
使用开发者工具将 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 生成自定义登录态并返回给小程序,整个过程非常复杂。而由于云开发与微信登录鉴权无缝整合,这些内容都不会涉及,所以这里不多介绍。
我们要获取用户信息,首先需要判断用户是否允许,可以从 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 对象里包含着当前登录用户的昵称、头像、性别等信息。
但是这个数据是在 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 指向有不同。
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 了。
那我们如何在页面的 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 是异步获取的信息,这里涉及到的异步,我们之后会详细介绍。
在我们使用 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 按钮后,数据就渲染出来了。
这种方式只能在 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,
})
},
关于用户登录以及信息获取,这里我们只是梳理了一些比较核心的知识点,还有一些大家可以去参考模板小程序里的代码,这里有一套相对比较完整的案例。更具有实际开发意义的用户登录,之后会在云开发部分介绍。
本文出自 李东bbsky