먼저 비교적 전통적인 주문 앱인 프로젝트에 대해 간단히 소개하겠습니다.
인터페이스는 그림과 같습니다.
왼쪽은 카테고리 메뉴이고 오른쪽은 긴 목록입니다. 단일 카테고리를 스크롤한 후 계속 스크롤할 수 있습니다. 동시에 다음 카테고리로 전환됩니다. 동시에 왼쪽 카테고리 메뉴를 선택하면 현재 상품 목록에 표시된 카테고리로 전환됩니다.
더 나은 사용자 경험을 고려하고 Meituan과 같은 애플릿 주문을 참고하여 이 제품 목록의 데이터를 한 번에 반환합니다. 현재 직면한 문제는 제품 수가 많을 때 첫 번째 렌더링 시간이 매우 길어지고 페이지가 정지된다는 것입니다.
Xiaoshengbb: 사실 원래 코드(역사적인 이유로)가 너무 형편없게 작성되었습니다...OTL
그림을 먼저 보겠습니다
Xiaoshengbb: 심지어 작은 프로그램을 읽었습니다. 더 이상 진행되지 않으면 경고를 주세요
WeChat 개발자 도구에는 모두 경고가 있고 프롬프트에서도 특정 코드의 위치를 찾으므로 핵심은 이 setData입니다.
! ! ! 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
최적화에 대한 몇 가지 공식 제안을 살펴보겠습니다. (developers.weixin.qq.com/miniprogram…) 구체적인 사례: setData
는 한 번에 너무 많은 데이터를 전송할 수 없습니다. list 너무 길면 별도로 렌더링할 수 있습니다(예: 2차원 배열로 변환하고 루프에서 매번 하나의 배열을 렌더링함). v1: 단순하고 투박한 버전
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) } }复制代码
위와 같이 작성하면 페이지 첫 화면 렌더링은 빠르나, 페이지 조작(구매 추가 버튼 등)을 클릭하면 문제가 발생합니다. , 페이지가 멈추고 작업 피드백이 심각하게 지연될 때까지 잠시 기다립니다.
사실 이 주기로 인해 단일 setData
의 수는 줄어들지만, setData
의 여러 주기로 바뀌기 때문입니다. , 다른 범주(다른 배열)는 여전히 렌더링 중이고 스레드는 여전히 사용 중입니다. JS 스레드가 렌더링을 컴파일하고 실행 중입니다. 클릭 이벤트가 적시에 논리 계층에 전달될 수 없으며 논리 계층이 작업 처리를 통과할 수 없습니다. 결과는 시간에 맞춰 뷰 레이어에 전달됩니다.
setData
가 파티클 업데이트를 지원하고 특정 속성을 지정할 수 있는 오픈 소스 가상 목록 구성 요소도 있습니다. 🎜🎜예를 들어 추가 구매 및 기타 작업의 경우 제품 오른쪽 상단에 있는 작은 숫자를 업데이트해야 합니다. 🎜rrreeedata
에 페이지와 관련 없는 데이터를 저장하지 말고 setData
를 사용하여 업데이트하지 마세요. setData
가 페이지 렌더링을 실행하기 때문입니다. 🎜🎜예: 🎜rrreee🎜PS: 아니면 page
개체 아래에 마운트할 필요도 없이 일반 변수를 사용하여 직접 저장하면 됩니다. 🎜🎜4. 이미지 크기 최적화🎜🎜긴 목록의 이미지 크기가 제한되지 않으면 대량의 대용량 이미지가 많은 메모리를 차지하게 되어 iOS 클라이언트의 메모리 사용량이 증가하여 시스템이 애플릿 페이지를 재활용하십시오. 메모리 문제 외에도 큰 이미지로 인해 페이지 전환 지연이 발생할 수도 있습니다. 🎜🎜해결책은 현재 표시된 이미지 영역의 크기를 기준으로 딱 맞는 크기(이미지의 2x-3x)로 사진을 찍는 것입니다. 🎜🎜이미지에는 CDN을 사용하는 것이 좋습니다. 일반적으로 이미지 서비스를 제공하는 CDN 서비스 제공업체에서는 이미지 자르기를 위한 인터페이스를 제공하며 인터페이스는 원본 이미지 링크만 반환하고 프런트 엔드에서는 이미지 자르기를 위한 매개변수를 전달합니다. 필요에 따라. 구체적인 프런트 엔드 접근 방식은 공개 이미지 처리 방법을 작성하거나 이미지 구성 요소를 직접 캡슐화하는 것일 수 있습니다. 🎜🎜일반적으로 사용되는 이미지 CDN 서비스 제공업체의 이미지 자르기 API 문서를 첨부합니다: 🎜比如在该点餐页面进入时需要获取定位,然后根据定位获取最近的门店,前面两个接口都需要请求(具体可以根据业务需求),而最后如果获取到的距离最近的门店跟上次一样,则不需要重新获取店铺详情和商品数据。
还是该点餐页面流程,像上文说过的,进入页面时需要获取定位接口,等定位接口返回结果了再拿定位取值去获取距离最近的店铺,最后才是请求店铺和商品数据。
这三个接口是串行的。此时如果我们每个接口都弹出一个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,用户就没有感知,体验比较好。
需要关注接口的粒度控制。 因为有时候合并接口,前端可以减少一次请求,体验更好;但有时候如果接口的数据太多,响应太慢,就可以考虑是否某部分数据可以后置获取,让主要的页面内容先渲染出来,根据这个设计来拆分接口。
比如项目中的点餐页面,原来购物车数据和商品规格弹窗显示的详情数据都是在获取店铺商品接口一次性返回的,而这个接口本来由于设计需要一次返回所有商品,就会造成数据量太大,而且后端需要查询的表也更多。于是把获取购物车,和商品详情接口都拆分为单独的接口,获取店铺商品接口的响应时间就减少了,页面也能更快显示出来。
其实上面提到的逻辑优化和接口优化很多都是细节,并不是太高深的技术,我们平时迭代的时候就可以注意。而体验方面的优化则需要前端同学在前端技术以外更多关注用户体验和设计方面的知识啦,而且这也是一个有追求的前端应该具备的技能……←_←
所以嘛……技术路漫漫,大家共勉吧
相关免费学习推荐:微信小程序开发教程
위 내용은 소규모 프로그램 성능 최적화 실습 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!