⼩程序每次更改data里的数据都得setData、没有像Vue便利的watch监听、不能操作Dom,对于杂乱性场景不太好,之前不支撑npm,不支撑sass,less预编译处理言语。
其实,⼩程序开发过程中我们⾯对的是IOS和Android微信客户端和辅助开发的小程序开发者工具,根据官方文档,这三大运行环境也是有所区别的:
关于⼩程序的⽣命周期,可以分为两个部分来理解:应⽤⽣命周期和⻚⾯⽣命周期。
另外还有:
onError,程序报错调用。
onPageNotFound ,如果页面不存在了。
前台、后台定义:
当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。
onLoad: function(options) { // 页面加载完毕触发 },
onUnload: function() { // 页面卸载触发 },
onShow: function() { // 页面开始渲染触发 },
onHide: function() { // 页面切换到后台触发 },
onReady: function() { // 页面渲染完毕触发 },
onPullDownRefresh: function() { // 页面下拉刷新触发},
onReachBottom: function() { // 页面上滑触底触发 },
onShareAppMessage: function () {
// 页面点击转发触发,需要return一个object,只有定义了此方法,才会有转发的功能
return {
title: "分享的页面", // 分享页面的标题
path: "/pages/logs/logs" // 分享的页面的路径
}
},
onPageScroll: function(options) { // 页面滚动触发 }
onLoad和onShow的区别:
①onLoad先于onShow执行
②onshow在每次打开页面都会加载数据,可以用于数据在需要刷新的环境下(整个生命周期里,可执行多次)
onload只是在第一次进入页面会刷新数据,从二级页面回来不会重新加载数据 (整个生命周期里,只执行一次)
③获取参数并且只请求一次的事件放在 onLoad 里。
当前页面需要时时刷数据的请求多次的事件放在 onShow 里。
(1)wx.navigateTo (可带参)
(2)wx.navigateBack
(3)wx.redirectTo
(4)wx.switchTab
(5)wx.reLaunch
1、wx.navigateTo:保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。(可带参)
2、wx.navigateBack :关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层。(没有url属性,不可带参)
3、wx.redirectTo:关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。(可带参)
4、wx.switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面(不可带参)
5、wx.reLaunch:关闭所有页面,打开到应用内的某个页面(可带参)
总结:switchTab这种导航方式,不能带参,也就是说不能通过url进行传参,解决方法就是如果要跳转到tab页面,使用reLaunch方法
只能打开非tabBar页面
描述:
用于保留当前页面、跳转到应用内的某个页面,使用 wx.navigateBack可以返回到原页面。对于页面不是特别多的小程序,通常推荐使用 wx.navigateTo进行跳转, 以便返回原页面,以提高加载速度。当页面特别多时,则不推荐使用。
案例:
wx.navigateTo({ url: ‘pageD’ }) 可以往当前页面栈多推入一个 pageD,此时页面栈变成 [ pageA, pageB, pageC, pageD ]
拓展:
可通过 getCurrentPages() 获取当前的页面栈
wx.navigateTo验证页面栈最大10层限制
只能打开非tabBar页面
描述:关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages() 获取当前的页面栈,决定需要返回几层。
拓展:
①wx.navigateBack可以结合delta参数实现多级退回。
改api
的参数只有delta
,表示返回的页面数。若 delta
的取值大于现有可返回页面数时,则返回到用户进入小程序的第一个页面。若不填delta
的值时,就默认其为1(注意,默认值并非取0),即返回上一页面。
返回上一页:
backTo(){
wx.navigator({
delta:1
})
}
//或
backTo(){
wx.navigator({})
}
②结合页面栈返回pageA基层页面栈首页
//返回页面栈pageA基层页面
backTo(){
const page = getCurrentPages();//获取当前页面栈
wx.navigator({
delta:page.length - 1
})
}
注意:若关闭多层页面栈,是从最新页面往旧页面依次关闭,关到基层为止。
只能打开非tabBar页面
描述:关闭当前页面,跳转到应用内的某个页面。页面重定向,将页面重新定向到一个目标页面,并不能返回到上一个页面。
注意:wx.redirectTo和wx.navigateTo的区别。
wx.redirectTo()用于关闭当前页面,跳转到应用内的某个页面。当页面过多时,被保留页面会挤占微信分配给小程序的内存,或是达到微信所限制的10层页面栈。这时应该考虑选择 wx.redirectTo。
优缺点:这样的跳转可以避免跳转前页面占据运行内存,但返回时页面需要重新加载,增加了返回页面的显示时间。
只能跳转到带有tab的页面
描述:Tab切换,跳转的页面必须是app.json 中 tabBar 配置的页面。跳转到tabBar页面同时关闭其他非tabBar页面
描述:关闭所有页面,打开到应用内的某个页面。即重新启动, 可以打开任意页面。
wx.reLaunch()与 wx.redirectTo()的用途基本相同, 只是 wx.reLaunch()先关闭了内存中所有保留的页面,再跳转到目标页面(此时页面栈变为[pageA]仅仅1层)。
小结:
首先先来了解一下微信小程序的运行环境:
小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。
我们可以看到,一个页面使用一个 WebView 线程进行渲染。如果打开10个页面,则会开启 10 个 WebView 线程,此时内存中的十个webView线程我们称之为页面栈。当然小程序也会对这块内存做限制,目前页面栈的限制是不能超过十条。在小程序中页面的路由是小程序框架本身控制的我们不要去手动管理, 小程序框架通过一个页面栈的设计来管理所有的界面,当发生路由跳转时,页面栈就会做出相应的变化,在小程序页面中通过 getCurrentPages() 就可以获取到当前的页面栈。
案例:以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面
注:?
后面为你要传递的值
一个参数:detail页面的 onload方法内接收
<navigator url="../detail/detail?good_id=16">navigator>
onLoad:function(options){
this.setData({
good_id:options.good_id
})
}
如果需要传多个参数, 用 & 链接即可
<navigator url="../detail/detail?good_id=16&good_price=1.88&good_name=哈哈哈">navigator>
onLoad:function(options){
this.setData({
good_id: options.good_id,
good_price: options.good_price,
good_name: options.good_name
})
}
如果要传 数组, 字典等复杂类型, 要先用 JSON.stringify() 转成字符串传递.
onshow() 方法中接受, 因为再次返回只执行onshow()方法.
在onShow里面把数据渲染到页面上
这样就把 address 传递并接受了
注 :
①这里 这个 route 是一个属性,在最新的小程序中,已经可以用 route 这个属性替换,即图中的
arr[arr.length - 2].route 与 arr[arr.length - 2].route 等效。
②这个方法适合 往后面传值(即已经存在的页面), 这样才能在栈中找到并主动写入数据, 且 一定要在 onshow() 方法中接受, 因为再次返回只执行onshow()方法.
注:这里还有一个同步 wx.setStorageSync和wx.getStorageSync,使用方法一样。看到这里大家应该注意到,结尾有Sync的为同步
同异步区别:
1、同步方法会堵塞当前任务,直到该同步的方法处理结束。
2、异步方法不会塞当前任务。
在 app.js 中定义全局变量
globalData: {
userInfo: null,
globalName:"lhs"
}
在其他页面可以取到全局变量
//在其他页面的js文件里
let app = getApp();
console.log(app.globalData.globalName)
在bindtap
定义的点击方法swiperTap : function(e)
中获取
let id = e.currentTarget.id;
注 : 这里 data-index="{{index}}"
里的 {{index}}
是有效的, 在用wx-for
渲染视图层时, index 代表点击的下标. 在bindtap
定义的点击方法 swiperTap : function(e)
中获取, 即
let index = e.currentTarget.dataset.index;
input输入框:
获取input的值:
1、获取input输入的值 wxml代码如下
<input bindinput="bindModel" type="text" class="input" placeholder="请输入兑换码" value='{{exchangeCode}}' data-model="exchageCode"/>
2、js代码
data: {
exchangeCode:"",
},
bindModel(e) {
let key = e.target.dataset.model;
this.setData({
[key]: e.detail.value
});
},
双线程就是渲染层(wxml、wxss)和逻辑层(js)的分离:
逻辑层把数据变化通知到视图层,触发视图层页面更新;视图层把触发的事件通知到逻辑层,进行业务处理。
在渲染层,运行环境会把WXML转化对应的JS对象,在逻辑层发生数据变变更的时候,我们需要通过运行环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的Ui界面
WXML可以先转成JS对象,然后再渲染出真正的DOM树。
通过setData把msg数据从“Hello World”变成“Goodbye”,产生的JS对象对应的节点就会发生变化,此时可以对比前后两个JS对象变化的部分,然后把这个差异应用到原来的DOM树上,从而达到更新UI的目的,这就是“数据驱动”的原理
这样设计的原因是,为了管控和安全,防止开发者使用一些浏览器提供的,诸如跳转页面,操作DOM,动态执行脚本的开放性接口。
于是微信小程序通过提供一个沙箱环境来执行开发者们的js代码来解决。这个沙箱环境只提供纯js的解析环境,没有任何浏览器相关接口。
把开发者的js逻辑代码放到单独的线程去运行,但在WebView线程里,开发者就没法直接操作DOM。
那如何实现动态更改界面呢?
逻辑层和渲染层的通信会由Native(微信客户端)做中转,逻辑层发送网络请求也经由Native转发。这说明可以把DOM的更新通过简单的数据通信来实现。
Virtual DOM 过程:用 JS 对象模拟 DOM 树 -> 比较两棵虚拟 DOM 树的差异 -> 把差异应用到真正的 DOM 树上。
双线程模型是小程序框架五业界大多数前端 Web框架不同之处,基于这个模型,可以更好地管控以及提供更安全的环境。
小程序的基础库是 JavaScript 编写的,它可以被注入到渲染层和逻辑层运行。主要用于:
①在渲染层,提供各类组件来组建界面的元素
②在逻辑层,提供各类 API 来处理各种逻辑
③处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑
由于小程序的渲染层和逻辑层是两个线程管理,两个线程各自注入了基础库。小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。这样可以:
①降低业务小程序的代码包大小
②可以单独修复基础库中的 Bug,无需修改到业务小程序的代码包
Exparser 框架
Exparser 是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,都由 Exparser 组织管理。Exparser 特点包括:
①基于 Shadow DOM 模型:模型上与 WebComponents 的 ShadowDOM 高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他API以支持小程序组件编程。
②可在纯JS环境中运行:这意味着逻辑层也具有一定的组件树组织能力。
③高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。
小程序发动会有两种状况,一种是「冷发动」,一种是「热发动」
小程序冷发动时假设发现有新版本,将会异步下载新版本的代码包,并一起用客户端本地的包进行发动,即新版本的小程序需求等下一次冷发动才会应用上。
假设需求马上应用最新版本,能够运用wx.getUpdateManagerAPI
进行处理。
wepy
和mpvue
,wepy
相对更有历史一些,更成熟,mpvue
很久没有在维护了,不太推荐taro
原生小程序通过Page提供的setData方法来绑定数据,如:
this.setData({title: 'this is title'});
WePY使用脏数据检查对setData进行封装,在函数运行周期结束时执行脏数据检查,一来可以不用关心页面多次setData是否会有性能上的问题,二来可以更加简洁去修改数据实现绑定,不用重复去写setData方法。
this.title = 'this is title';
需注意的是,在异步函数中更新数据的时候,必须手动调用$apply方法,才会触发脏数据检查流程的运行。如:
setTimeout(() => {
this.title = 'this is title';
this.$apply();
}, 3000);
在执行脏数据检查时,会通过this.$$phase
标识当前检查状态,并且会保证在并发的流程当中,只会有一个脏数据检查流程在运行
this.setData({})
不仅会改变数据,而且还会改变视图。用于将数据从逻辑层发送到视图层(异步),同时改变对应的this.data
的值(同步)this.data
会造成数据会变,但是视图并不会更新总结一下就是:this.data与this.setData的关系就是this.setData里面存储的是this.data的副本,而界面是从this.setData里面托管的this.data的副本取数据的。所以我们更改this.data并不会直接更新界面,因为这个时候的this.setData里面的副本还是没有更新前的。
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。
频繁的去 setData。
在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
每次 setData 都传递大量新数据。
由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程,
后台态页面进行 setData。
当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。
图片资源:
目前图片资源的主要性能问题在于大图片和长列表图片上,这两种情况都有可能导致 iOS 客户端内存占用上升,从而触发系统回收小程序页面。
图片对内存的影响:
在 iOS 上,小程序的页面是由多个 WKWebView 组成的,在系统内存紧张时,会回收掉一部分 WKWebView。从过去我们分析的案例来看,大图片和长列表图片的使用会引起 WKWebView 的回收。
图片对页面切换的影响:
除了内存问题外,大图片也会造成页面切换的卡顿。我们分析过的案例中,有一部分小程序会在页面中引用大图片,在页面后退切换中会出现掉帧卡顿的情况。当前我们建议开发者尽量减少使用大图片资源。
代码包大小的优化:
小程序一开始时代码包限制为 1MB,但我们收到了很多反馈说代码包大小不够用,经过评估后我们放开了这个限制,增加到 2MB 。代码包上限的增加对于开发者来说,能够实现更丰富的功能,但对于用户来说,也增加了下载流量和本地空间的占用。开发者在实现业务逻辑同时也有必要尽量减少代码包的大小,因为代码包大小直接影响到下载速度,从而影响用户的首次打开体验。除了代码自身的重构优化外,还可以从这两方面着手优化代码大小:
控制代码包内图片资源:
小程序代码包经过编译后,会放在微信的 CDN 上供用户下载,CDN 开启了 GZIP 压缩,所以用户下载的是压缩后的 GZIP 包,其大小比代码包原体积会更小。 但我们分析数据发现,不同小程序之间的代码包压缩比差异也挺大的,部分可以达到 30%,而部分只有 80%,而造成这部分差异的一个原因,就是图片资源的使用。GZIP 对基于文本资源的压缩效果最好,在压缩较大文件时往往可高达 70%-80% 的压缩率,而如果对已经压缩的资源(例如大多数的图片格式)则效果甚微。
及时清理没有使用到的代码和资源:
在日常开发的时候,我们可能引入了一些新的库文件,而过了一段时间后,由于各种原因又不再使用这个库了,我们常常会只是去掉了代码里的引用,而忘记删掉这类库文件了。目前小程序打包是会将工程下所有文件都打入代码包内,也就是说,这些没有被实际使用到的库文件和资源也会被打入到代码包里,从而影响到整体代码包的大小。
主要的优化策略可以归纳为三点:
监听器的原理,是将data中需监听的数据写在watch对象中,并给其提供一个方法,当被监听的数据的值改变时,调用该方法。
我们需要用到Javascript中的Object.defineProperty()方法,来手动劫持对象的getter/setter,从而实现给对象赋值时(调用setter),执行watch对象中相对应的函数,达到监听效果。
Object.defineProperty()方法,会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
如果次数过多,应该页面会被卡死,数据可能成功,但页面出不来。
建议:把所有要 setData 的数据都存放进对象里面,等执行的差不多的时候再一次性通过 setData 方法把数据更新过去
JsCore全称为JavaScriptCore ,是一款 JavaScript 引擎,通常会被叫做虚拟机
WebView也就是我们熟悉的“网络视图”,能加载并显示网页,可以将其视为一个浏览器。
我们的功能里面有个滚动到底部加载的功能,优化前我们的做法是这样的
<!--只阐述逻辑,非真实代码-->
// 1: 初始一个list,存储列表数据
data = startList
// 2: 监听滚动事件,滚动到底部获取新数据,并追加到list尾部,最后重新setData
onReachBottom:()=>{
const {list} = this.data
fetchNewData().then((res)=>{
list.push(res.list);
this.setData({list})
}
这样会导致list的数据会越来越大,每次setData的数据就会越来越多,因而每次页面重新渲染的节点就会越来越多,从而导致滚动到后面,加载越来越慢。而且小程序本身对数据大小也有限制,不能超过1M。
怎么解决呢?
小程序setData里面的key支持数据路径的写法,比如
let o = obj;
this.setData({
'o.属性':value
})
或者
let a = array;
this.setData({
'array[0].text':value
})
所以我们可以通过数据路径的写法,来将数据分批的传输到视图层中,减少一次性setData的数据大小。具体写法如下
// 1.通过一个二维数组来存储数据
let feedList = [[array]];
// 2.维护一个页面变量值,加载完一次数据page++
let page = 1
// 3.页面每次滚动到底部,通过数据路径更新数据
onReachBottom:()=>{
fetchNewData().then((newVal)=>{
this.setData({
['feedList[' + (page - 1) + ']']: newVal,
})
}
}
// 4.最终我们的数据是[[array1],[array2]]这样的格式,然后通过wx:for遍历渲染数据
图片懒加载
微信提供了IntersectionObserver对象。IntersectionObserver 对象,用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。
通过这个api我们不用再主动去监听元素位置了,在页面渲染一开始,通过这个api指明需要监听的元素,系统会自动去监听了元素位置。
intersectionRatio值大于0,说明元素出现在视图中了,重新setData数据,显示图片组件。
let data = list;
<img class="img-{{index}}" wx:for="{{data}}"></img>
data.forEach((item,index)=>{
this.createIntersectionObserver().relativeToViewport.observe(`.img-${index}`,res=>{
if (res.intersectionRatio > 0){
this.setData({
item.imgShow:true
})
}
})
CDN图片处理
对于页面里面的图片,最好都把图片存储在cdn服务器上。一个是能充分利用cdn缓存来加快请求速度,另外一个就是cdn上能够将图片进行一定的处理,比如裁剪。我就是通过cdn来响应图片处理,然后请求图片时告诉cdn服务器需要什么要的尺寸图片,由cdn服务器响应对应尺寸图片。
key值在列表渲染的时候,能够提升列表渲染性能。
小程序的页面是渲染:
①将wxml结构的文档构建成一个vdom虚拟数
②页面有新的交互,产生新的vdom数,然后与旧数进行比较,看哪里有变化了,做对应的修改(删除、移动、更新值)等操作
③最后再将vdom渲染成真实的页面结构
key值的作用就在第二步,当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。
全局变量,常量,本地缓存,存于后端数据等
setData的时候会调evaluateJavascript
这个函数解析传入的数据,会带来webView中js引擎资源的占用
问题:后台返回数据中有可能包含了大量的无用数据,数据量如果过大时候会对小程序渲染界面有影响吗?
回答:有。
程序的视图层目前使用 WebView
作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView
和JavascriptCore
都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript
所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。由于小程序运行逻辑线程与渲染线程之上,setData的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间。
所以最好是新建一个tempData
,将要的数据取出来之后再setData这个tempData,以此来提高微信小程序的页面渲染速度,提升微信小程序运行效率,优化微信小程序的用户体验。
传统H5在启动时,page1.html 只会加载 page1.html 的页面与逻辑代码,当page1.html 跳转至 page2.html 时,page1 所有的 Javascript 数据将会从内存中消失。page1 与 page2 之间的数据通信只能通过 URL 参数传递或者浏览器的 cookie,localStorge 存储处理。
小程序在启动时,会直接加载所有页面逻辑代码进内存,即便 page2 可能都不会被使用。在 page1 跳转至 page2 时,page1 的逻辑代码 Javascript 数据也不会从内存中消失。page2 甚至可以直接访问 page1 中的数据。
对于上述问题,WePY 中封装了两种概念去解决:
扩展了生命周期,添加了onPrefetch事件,会在 redirect 之时被主动调用。同时给onLoad事件添加了一个参数,用于接收预加载或者是预查询的数据:
// params
// data.from: 来源页面,page1
// data.prefetch: 预查询数据
// data.preload: 预加载数据
onLoad (params, data) {}
用于 page1 主动传递数据给 page2,比如 page2 需要加载一份耗时很长的数据。我可以在 page1 闲时先加载好,进入 page2 时直接就可以使用。
预加载数据示例:
// page1.wpy 预先加载 page2 需要的数据。
methods: {
tap () {
this.$redirect('./page2');
}
},
onLoad () {
setTimeout(() => {
this.$preload('list', api.getBigList())
}, 3000)
}
// page2.wpy 直接从参数中拿到 page1 中预先加载的数据
onLoad (params, data) {
data.preload.list.then((list) => render(list));
}
用于避免于 redirecting 延时,在跳转时调用 page2 预查询。
预查询数据示例:
// page1.wpy 使用封装的 redirect 方法跳转时,会调用 page2 的 onPrefetch 方法
methods: {
tap () {
this.$redirect('./page2');
}
}
// page2.wpy 直接从参数中拿到 onPrefetch 中返回的数据
onPrefetch () {
return api.getBigList();
}
onLoad (params, data) {
data.prefetch.then((list) => render(list));
}
优化了setData的数据绑定,不管中间进行了什么操作,在运行结束时执行一次脏检查,对需要设置的数据进行setData操作。
注意:在开发过程中,一定要避免同一流程内多次 setData 操作,否则非常的损耗性能。
setTimeout(() => {
this.label = 'label';
this.$apply();
})
wepy 主文件 .wpy => style + template + script
但wepy有一套自建的api拦截器
// config + success + fail + complete
this.intercept('request', {
config() {},
success() {}
})
针对拦截请求的相关面试题
wepy实现了同vue一样,可以复用抽离的方式:
相关面试题考察:(事件)响应顺序是如何的?
mixin事件的响应顺序和vue是相反的。vue:先执行mixin函数,再执行组件本身函数;而兼容性混合中,先响应了组件本身,在响应混合事件。
wepy的组件化 < = > js原生模块化
a.wpy
- data: {d: d}
- methods: m()
b.wpy
- data: {d: d}
- methods: m()
// 编译后
a.wxml + a.wxss + a.js
- data: $a.$d
- methods: $a.$m()
b.wxml + b.wxss + b.js
- data: $b.$d
- methods: $b.$m()
相关面试题:
// wepy中的错误示范
<template>
<view>
<comp></comp>
</view>
<view>
<comp></comp>
</view>
</template>
<script>
import comp from './comp';
// ……
</script>
// 正确示范
<template>
<view>
<comp></comp>
</view>
<view>
<newComp></newComp>
</view>
</template>
<script>
import comp from './comp';
// ……
// {
// comp: comp,
// newComp: comp
// }
</script>
// error
// list.wpy
<view>{{ item.name }}</view>
// index.wpy
<repeat for="{{ list }}">
<list :item.sync="item">
</repeat>
// ok
// list.wpy
<repeat for="{{ list }}">
<view>{{ item.name }}</view>
</repeat>
// index.wpy
<list :item.sync="item">
// 原因还是静态组件
// 入口
// wepy-cli/src/compile.js
适配器 - adapter
node => newNode
components.js
wepy.js
$$phase - 是否有脏值检查正在运行
$apply - 应用更新(1. 通过phase做状态检查 2. 调用$digest做值统一更新)
$digest - 遍历originData,做脏值对比,如果值有更新,放到readyToSet。循环之后统一调用setData
登录通过输入用户名和密码返回token,后面用Token去请求其他接口
参考文档:微信小程序登录授权详解
三者之间的通信:小程序(前端),dev_svr本地开发的服务,wx_svr第三方微信的服务
wx.login()
接口,获取code
他就是这次登录的标识码,把和这个code
传给本地的svr。需要注意的是,这个code只能获取一次,如果后面超时的话,只能重新后面的交互请求,这个 code不能重新获取;code + appId + appSecret
与微信的服务端交互;session_key + openId
给本地的svr标识当前通话的独立性code + session_key + openId
与本地业务的登录态token做关联,并且将生成的token传给前端。storage
缓存,并且在后面的请求中带上token静默登录失败了是访客态
,成功时游客态
接口地址:
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
请求参数:
返回参数:
后续用户重新进入小程序,调用wx.checksession()
监测登录状态,如失效,则重新发起登录流程。
wx.checkSession({
success: function(){
//session 未过期,并且在本生命周期一直有效
},
fail: function(){
//登录态过期
wx.login() //重新登录
....
}
})
分为两种:一个是微信授权登录(手机号登录),另一个是密码登陆
手机号登录的getPhoneNumber()
方法是必须绑定在Button按钮上才能触发
用户登录了是会员态
wx.getSetting
用户可以在小程序设置界面(右上角 - 关于 - 右上角 - 设置)中控制对该小程序的授权状态。
开发者可以使用 wx.getSetting 获取 用户当前的授权状态。
object参数说明:
// 先查询一下用户是否授权了 "scope.record" 这个 scope
wx.getSetting({
success(res) {
if (!res.authSetting['scope.record']) {
wx.authorize({
scope: 'scope.record',
success() {
// 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
wx.startRecord()
}
})
}
}
})
wx.getUserInfo
获取用户信息,withCredentials 为 true 时需要先调用 wx.login 接口。
success返回参数说明:
注:当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息。
userInfo参数说明:
示例代码:
wx.getUserInfo({
success: function(res) {
var userInfo = res.userInfo
var nickName = userInfo.nickName
var avatarUrl = userInfo.avatarUrl
var gender = userInfo.gender //性别 0:未知、1:男、2:女
var province = userInfo.province
var city = userInfo.city
var country = userInfo.country
}
})
getPhoneNumber
因为需要用户主动触发才能发起获取手机号接口,所以该功能不由 API 来调用,需用 组件的点击来触发。
使用步骤:
需要将 组件 open-type 的值设置为 getPhoneNumber。当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。
在回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。
代码示例:
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"> </button>
Page({
getPhoneNumber: function(e) {
console.log(e.detail.errMsg)
console.log(e.detail.iv)
console.log(e.detail.encryptedData)
}
})
返回参数说明:
encryptedData 解密后为以下 json 结构,包含信息:
getUserInfo
和getUserProfile
的区别?微信官方阉割了 wx.getUserInfo 接口,前端需要使用新的接口 wx.getUserProfile 替代
不推荐使用
wx.getUserInfo
或
a.将不再弹出弹窗
b.不可获取用户个人信息(头像、昵称、性别与地区),将直接获取 匿名数据(包括userInfo
与encryptedData
中的用户个人信息)
c.获取加密后的 openID 与 unionID 数据的能力不做调整。
新增并推荐使用wx.getUserProfile
a.产生 点击事件(例如 button 上 bindtap 的回调中)后才可调用
b.每次请求都会弹出授权窗口
c.用户同意后返回 userInfo
d. 返回的加密数据中 不包含 openId 和 unionId 字段!这一点很重要!
使用wx.getUserProfile
的两个前提条件:
1.开发者工具版本不低于 1.05.2103022
2.基础库版本不低于 2.10.4
所以说:
①使用wx.login()
获取unionid和code
②使用getUserProfile
弹出信息个人授权,获取用户信息
window: {
"backgroundTextStyle": "light",
// ……
}
window: {
"default": "light",
// ……
}
tabBar: {
// 支付宝
items: [],
// 微信
list: []
}
a:for="{{list}}"
key="item-{{index}}" index="index"
wx:for="{{list}}"
wx:key="key" wx-for-item="item"
a:if
a:else
a:esleif
wx:if
wx:else
wx:esleif
my.showToast({
})
wx.showToast({
})
my.showLoading({
})
wx.showLoading({
})
my.httpRequest({
url: '',
method: '',
data: {},
header: '',
dataType: '',
success: function() {},
fail: function() {}
})
wx.request({
url: '',
method: '',
data: {},
header: '',
dataType: '',
success: function() {},
fail: function() {}
})
my.tradePay({
tradeNO: '47983279478923797057247185',
success: res => {},
fail: res => {}
})
wx.requstPayment({
package: 'pre_pay_id',
signType: 'MD5',
paySign: '',
success: res => {},
fail: res => {}
})
my.getAuthCode({
success() {
}
})
wx.login({
success() {}
})
App({
require: function($uri) {
return require($url);
}
})
组件中使用:
const Api = app.require('utils/tool.js');
利用require返回uri带上/
const money = 345678;
<view>{{ money }}</view>
期望: ‘34万元’
vue是这样: {{ money | moneyFilter }}
小程序是这样:利用wxs的format
.wxs
const fnToFixed = function(num) {
return num.toFixed(2);
}
module.exports = {
fnToFixed
}
//引入
<wxs src='../../../xxx.wxs' module="filters">
//使用
<view>{{ filters.fnToFixed(money) }}</view>
错误示范:
这个方法会把b里面的其他属性都去掉了。
data: {
a: '1',
b: {
c: 2,
d: 3
}
}
this.setData({
b: {
c: 4
}
});
正确示范:
先提取出来或者用wx-update-data
const { b } = this.data;
b.c = 4;
this.setData({ b });
// wx-update-data
微信里面的接口像wx.login()
这样的方法,我们获取他的结果都是通过回调获取他的信息
// wx-promise-pro
npm i -S wx-promise-pro
import { promisifyAll } from 'wx-promise-pro';
// before
wx.showLoading({
success: res => {
wx.request({
// ……
})
}
})
// after
wx.pro.showLoading({
title: 'loading',
mask: true
}).then(() => {
consol.log('I am in promise');
})
wx.request({
url: '',
data: (),
success: res => {}
})
// 实现
wx.pro.request({
url: '',
data: ()
}).then(res => {}).catch(res => {})
//一种写法
function request(opt) {
return new Promise( (resolve, reject) => {
wx.request({
...opt,
success: res => {
resolve(res);
},
fail: err => {
reject(err);
}
})
} )
}
//更加抽象的另一种写法
function promisify(api) {
return (opt = {}) => {
return new Promise((resolve, reject) => {
api({
...opt,
success: resolve,
fail: reject
})
})
}
}
promisify(wx.request)(opt);
class Promise {
}
var app = getApp()
app.getUserInfo()
a. 如果在App()内部函数中,如果要获取全局getApp,直接用this即可
b. 获取getApp之后,可不可以拿到生命周期?可以,但是禁止操作,调用的目的是为了获取全局变量,不要修改
a. getCurrentPage()获取当前page,不要修改页面栈,不要在app.onLaunch时候调用,page还没生成
可以运用在父子组件或点击下一个页面传值回上一个页面
一、当前页面跳转下一页注册监听 events,监听被打开页面的回调
wx.navigateTo({
url: 'home?id=1',
<!-- events 监听被打开页面发送到当前页面的数据 -->
events: {
<!-- 给指定事件添加监视器,获取被打开页面传回当前页面的数据 -->
<!-- 被打开页面进行回调 -->
accessDataForm: function(data) {
console.log(data)
},
homeEvent: function(data) {
console.log(data)
}
...
},
success: function(res) {
<!-- 通过eventChannel向被打开页面传值 -->
res.eventChannel.emit('accData', { data: 'id_number' })
}
})
二、被打开的页面调用
Page({
onLoad: function(option){
<!-- 获取事件对象 -->
const eventChannel = this.getOpenerEventChannel()
<!-- 通知上一页,传回参数,响应函数 -->
<!-- 改变上一页监听的数据时调用 -->
eventChannel.emit('accessDataForm', {data: 'id_number'});
eventChannel.emit('homeEvent', {data: 'id_number'});
<!-- 监听accData事件,获取上一页面通过eventChannel传到当前页面的数据 -->
eventChannel.on('accData', function(data) {
console.log(data)
})
}
})
属性名应避免以 data 开头,即不要命名成dataXyz这样的形式,因为在 WXML 中,data-xyz=""会被作为节点 dataset 来处理,而不是组件属性。
部分内容参考:https://blog.csdn.net/weixin_33994444/article/details/91361960