Lassen Sie mich zunächst kurz das Projekt vorstellen, bei dem es sich um eine relativ konventionelle Bestell-App handelt.
Die Benutzeroberfläche ist wie in der Abbildung dargestellt:
Die linke Seite ist das Kategoriemenü und die rechte Seite ist die lange Liste. Nachdem Sie durch eine einzelne Kategorie gescrollt haben, können Sie weiterblättern und wechseln Sie zur nächsten Kategorie. Gleichzeitig wird der Status der Auswahl des Kategoriemenüs auf der linken Seite angezeigt. Dann wird zur Kategorie in der aktuellen Produktliste gewechselt.
Im Hinblick auf eine bessere Benutzererfahrung und unter Bezugnahme auf Meituan und andere Bestell-Applets werden die Daten dieser Produktliste auf einmal zurückgegeben. Das derzeit auftretende Problem besteht darin, dass bei einer großen Anzahl von Produkten die erste Renderzeit sehr lang ist und die Seite einfriert.
Xiaoshengbb: Tatsächlich war der ursprüngliche Code (aus historischen Gründen) zu schlecht geschrieben ... OTL
Lassen Sie mich zuerst ein Bild einfügen
Xiaoshengbb: Sogar das Das kleine Programm wurde gelesen. Wenn es nicht mehr funktioniert, geben Sie mir bitte eine Warnung ! ! !
Wir können uns zunächst einige offizielle Vorschläge für die Leistung von Miniprogrammen und die setData
-Optimierung ansehen. (developers.weixin.qq.com/miniprogram…) setData
!!!
我们可以先看看官方对于小程序性能以及 setData
优化的一些建议。(developers.weixin.qq.com/miniprogram…)
具体实践:
setData
不能一次性传太多数据,如果列表太长,可以分开渲染【比如转化为二维数组,每次循环渲染一个数组】。v1:简单粗暴版
// 每次渲染一个分类// 假设goodsList是一个二维数组goodsList.forEach((item, index) => { this.setData({ [`goodsList[${index}]`]: item }) })复制代码
像上面这样写会有一个问题,页面首屏渲染是快了,但是点击页面操作(比如加购按钮等),页面会卡住,等一下才有反应,操作反馈延迟严重。
其实这是因为,这个循环是把单次 setData
数量减少了,但是却变成了循环多次 setData
,我们看着首屏显示好了,但是其实其他分类(其他数组)还在渲染,线程还是忙碌状态,JS 线程一直在编译执行渲染,点击事件不能及时传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层。
v2:定时器hack版
既然js线程忙着渲染,那我们可以强制让它先停下来。于是有了v2的定时器hack版。
// 每次渲染一个分类let len = data.goodsList ? data.goodsList.length : 0;let idx = 0let timer = setInterval(() => { if (idx < len) { that.setData({ [`goodsList[${idx}]`]: data.goodsList[idx] }); idx++ } else { clearInterval(timer) } }, 15);复制代码
现在首屏渲染速度问题解决了,点击按钮延迟响应问题也解决了。就是代码有点hack,逼死强迫症
v3:大杀器——虚拟列表
虚拟列表简单说原理就是只渲染当前显示屏幕区域以及前n屏和后n屏的数据,用一个单独的字段保存当前需要显示的数组(就是当前一屏+前n屏+后n屏),每次列表滚动的时候重新计算需要显示的数据,更新这个字段,页面就会相应更新了。这样就能保证页面上的元素节点数量不会太多,就可以支持大量数据的长列表需求。
更详细的原理和实现各位同学们可以自己搜一下,此处不展开。
小程序官方也有开源的虚拟列表组件:recycle-view
setData
可以支持颗粒更新,指定到具体的属性。比如加购等操作,需要更新商品右上角的小数字,可以这样写:
this.setData({ [`goodsList[${categoryIndex}][${goodsIndex}].num`]: goodsItem.num })复制代码
data
,不要用 setData
更新,因为 setData
会触发页面渲染。eg:
Page({ data: { ... }, // 跟页面渲染无关的数据 state: { hasLogin: false, }, ... })// 更新的时候直接赋值就行this.state.hasLogin = true复制代码
PS:或者甚至不需要挂载到 page
setData
kann nicht zu viele Daten auf einmal übertragen, wenn die list Wenn es zu lang ist, kann es separat gerendert werden [z. B. in ein zweidimensionales Array konvertiert und jedes Mal ein Array in einer Schleife gerendert werden]. v1: Einfache und grobe Version
let showLoadingTimer = null;let showRequestLoading = false; // 标记是否正在显示loading/** * 封装request * @param {*} {showLoading:是否需要显示loading, options:request参数,如url,data等} */function request({showLoading = true, ...options}) { // 显示request loading handleShowLoading(showLoading) wx.request({ ... complete() { // 关闭request loading handleShowLoading(false) } }) }/** * 封装request loading * 短时间内如果调用多次showLoading,会合并在一起显示,而不是每个都闪现一下 * @param showLoading */function handleShowLoading(showLoading) { if (showLoading) { // 显示loading clearTimeout(showLoadingTimer); if (!showRequestLoading) { showRequestLoading = true; wx.showNavigationBarLoading(); wx.showLoading({ title: "加载中", mask: true }) } } else { // 200ms后关闭loading showLoadingTimer = setTimeout(() => { showRequestLoading = false; wx.hideNavigationBarLoading(); wx.hideLoading() }, 200) } }复制代码
Beim Schreiben wie oben ist ein Problem aufgetreten. Die erste Bildschirmdarstellung der Seite ist schnell, aber wenn Sie auf den Seitenvorgang klicken (z. B. auf die Schaltfläche „Kauf hinzufügen“ usw.). , bleibt die Seite hängen und wartet eine Weile, bevor die Rückmeldung zum Vorgang stark verzögert wird.
Tatsächlich liegt das daran, dass dieser Zyklus die Anzahl einzelner setData
reduziert, sich aber in mehrere Zyklen von setData
verwandelt. Wir sehen uns die erste Bildschirmanzeige an, aber tatsächlich , andere Kategorien (andere Arrays) werden noch gerendert und der JS-Thread ist immer noch mit dem Rendern beschäftigt. Das Klickereignis kann nicht rechtzeitig an die Logikschicht übergeben werden, und die Logikschicht kann die Operationsverarbeitung nicht bestehen Ergebnisse rechtzeitig in die Ansichtsebene übertragen.
v2: Timer-Hack-Version
🎜Da der JS-Thread mit dem Rendern beschäftigt ist, können wir ihn zwingen, zuerst anzuhalten. Es gibt also eine Timer-Hack-Version von v2. 🎜rrreee🎜Jetzt wurde das Problem der Rendergeschwindigkeit des ersten Bildschirms gelöst, und auch das Problem der verzögerten Reaktion beim Klicken auf Schaltflächen wurde gelöst. Es ist nur so, dass der Code etwas hackig ist, was dazu führt, dass ich an einer Zwangsstörung leide die ersten n Bildschirme und die letzten n Bildschirme unter Verwendung eines separaten Felds. Speichern Sie das Array, das aktuell angezeigt werden muss (d. h. den aktuellen Bildschirm + die vorherigen n Bildschirme + die nächsten n Bildschirme), und berechnen Sie die Daten neu, die alle angezeigt werden müssen Wenn Sie durch die Liste scrollen, aktualisieren Sie dieses Feld und die Seite wird entsprechend aktualisiert. Dadurch wird sichergestellt, dass die Anzahl der Elementknoten auf der Seite nicht zu groß ist und lange Listenanforderungen für große Datenmengen unterstützt werden können. 🎜🎜Studenten können selbst nach detaillierteren Prinzipien und Implementierungen suchen und werden hier nicht erweitert. 🎜🎜Das offizielle Miniprogramm verfügt auch über eine Open-Source-Komponente für virtuelle Listen: recycle-view🎜setData
kann Partikelaktualisierungen unterstützen und bestimmte Attribute angeben. 🎜🎜Für Vorgänge wie zusätzliche Einkäufe müssen Sie beispielsweise die kleine Zahl in der oberen rechten Ecke des Produkts aktualisieren. Sie können sie so schreiben: 🎜rrreeedata
und verwenden Sie nicht setData
zum Aktualisieren, da setData
das Rendern der Seite auslöst. 🎜🎜zB: 🎜rrreee🎜PS: Oder Sie müssen es nicht einmal unter dem page
-Objekt mounten, sondern speichern es einfach direkt mit einer gewöhnlichen Variablen. 🎜🎜4. Optimierung der Bildgröße🎜🎜Wenn die Bildgröße in der langen Liste nicht begrenzt ist, belegt eine große Anzahl großer Bilder viel Speicher, was dazu führen kann, dass die Speichernutzung des iOS-Clients zunimmt und das System dazu führt Recyceln Sie die Applet-Seite. Zusätzlich zu Speicherproblemen können große Bilder auch Verzögerungen beim Seitenwechsel verursachen. 🎜🎜Die Lösung besteht darin, ein Bild mit genau der richtigen Größe (2x-3x das Bild) aufzunehmen, basierend auf der Größe des aktuell angezeigten Bildbereichs. 🎜🎜Es wird empfohlen, ein CDN für Bilder zu verwenden. Im Allgemeinen stellen CDN-Dienstanbieter, die Bilddienste bereitstellen, eine Schnittstelle zum Zuschneiden von Bildern bereit. Die Schnittstelle gibt dann nur den ursprünglichen Bildlink zurück und das Frontend übergibt Parameter zum Zuschneiden des Bildes nach Bedarf. Der spezifische Front-End-Ansatz kann darin bestehen, eine öffentliche Bildverarbeitungsmethode zu schreiben oder die Bildkomponente selbst zu kapseln. 🎜🎜Anbei finden Sie das Bildzuschneide-API-Dokument häufig verwendeter Bild-CDN-Dienstanbieter: 🎜比如在该点餐页面进入时需要获取定位,然后根据定位获取最近的门店,前面两个接口都需要请求(具体可以根据业务需求),而最后如果获取到的距离最近的门店跟上次一样,则不需要重新获取店铺详情和商品数据。
还是该点餐页面流程,像上文说过的,进入页面时需要获取定位接口,等定位接口返回结果了再拿定位取值去获取距离最近的店铺,最后才是请求店铺和商品数据。
这三个接口是串行的。此时如果我们每个接口都弹出一个loading提示,就会出现loading显示一会儿,消失,又显示一会儿,又消失……这样的现象,这样的体验是不太好的。
建议可以通过封装请求,并且在请求里统一处理loading,来合并短时间内多次发起请求的多个loading。
eg:
let showLoadingTimer = null;let showRequestLoading = false; // 标记是否正在显示loading/** * 封装request * @param {*} {showLoading:是否需要显示loading, options:request参数,如url,data等} */function request({showLoading = true, ...options}) { // 显示request loading handleShowLoading(showLoading) wx.request({ ... complete() { // 关闭request loading handleShowLoading(false) } }) }/** * 封装request loading * 短时间内如果调用多次showLoading,会合并在一起显示,而不是每个都闪现一下 * @param showLoading */function handleShowLoading(showLoading) { if (showLoading) { // 显示loading clearTimeout(showLoadingTimer); if (!showRequestLoading) { showRequestLoading = true; wx.showNavigationBarLoading(); wx.showLoading({ title: "加载中", mask: true }) } } else { // 200ms后关闭loading showLoadingTimer = setTimeout(() => { showRequestLoading = false; wx.hideNavigationBarLoading(); wx.hideLoading() }, 200) } }复制代码
比如这个点餐页每次 onShow
都会调用定位接口和获取最近门店接口,但是不显示loading,用户就没有感知,体验比较好。
需要关注接口的粒度控制。 因为有时候合并接口,前端可以减少一次请求,体验更好;但有时候如果接口的数据太多,响应太慢,就可以考虑是否某部分数据可以后置获取,让主要的页面内容先渲染出来,根据这个设计来拆分接口。
比如项目中的点餐页面,原来购物车数据和商品规格弹窗显示的详情数据都是在获取店铺商品接口一次性返回的,而这个接口本来由于设计需要一次返回所有商品,就会造成数据量太大,而且后端需要查询的表也更多。于是把获取购物车,和商品详情接口都拆分为单独的接口,获取店铺商品接口的响应时间就减少了,页面也能更快显示出来。
其实上面提到的逻辑优化和接口优化很多都是细节,并不是太高深的技术,我们平时迭代的时候就可以注意。而体验方面的优化则需要前端同学在前端技术以外更多关注用户体验和设计方面的知识啦,而且这也是一个有追求的前端应该具备的技能……←_←
所以嘛……技术路漫漫,大家共勉吧
相关免费学习推荐:微信小程序开发教程
Das obige ist der detaillierte Inhalt vonÜbungszusammenfassung zur Leistungsoptimierung kleiner Programme. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!