# 外卖小程序首页商品列表和购物车的优化
在项目中,做了一个类似饿了么的外卖小程序,首页效果类似饿了么。但是实际商品多了之后存在卡顿问题,而饿了么明显很流畅,所以通过优化应该能够达到类似的效果。
# 展示方式
第一版是类似饿了么外卖的效果,左边展示商品分类,右边一个长列表展示所有商品。 当商品滚动时,监听滚动,左边自动切换当前分类;当点击左侧分类,右侧滚动到对应位置。同时,每个商品可点击+号,会出现一个小球动画,并向左展开为- 1 +。 滚动监听通过wx.createIntersectionObserver实现;而点击滚动可以用scroll-view的scroll-into-view来实现。
实际测试时,发现对于商品较多的店铺存在明显的卡顿。 发现饿了么的超市商品很多,采用了另外一种展示方式,即右侧列表只展示一个分类的商品,滚动到顶部或者底部时,继续上拉或者下拉,切换分组。 开始在scroll-view内顶部和底部增加提示文字,用touch事件监听,但是不好处理惯性滚动。然后用scroll-view嵌套scroll-view的方法,内部惯性滚动不会触发外侧滚动。再对外侧touch事件监听实现回弹。 发现还是明显卡顿。
# 长列表优化
由于是商品过多造成的卡顿,首先想到是长列表的优化。虽然由全部商品变成了一个分类的商品,但是还是存在大量的dom节点。 于是通过事件监听,来动态渲染商品。对于所有商品,只显示一个view标签用于占位,这个view标签的background用一张骨架图渲染。而对于出现在屏幕可视区域的商品,调用组件方法进行渲染,当商品滚动到可视区域之外,再次切换未骨架图。这样,在整个列表中,除了几个商品之外,外部只有一个dom节点,大大减少了节点数量。
// goods-item.wxml
<wxs src="../../commom/common.wxs" module="m0"></wxs>
<view class="category-goods skeleton" wx:if="{{skeleton || (lazy && !showGoods)}}">
</view>
<view class="category-goods" catch:tap="handleShowDetail" wx:else>
<view class="goods-img-wrapper">
<image></image>
</view>
<view class="category-cntt">
<view class="info">
...
</view>
<view class="right">
<goods-counter id="goods-{{goods.id}}" goods="{{goods}}" count="{{m0.getGoodsCount(goods.id, cartStore.flatList)}}" businessStatus="{{businessStatus}}" cartStore="{{cartStore}}" catch:skuSelect="handleShowSkuSelect"></goods-counter>
</view>
</view>
</view>
</view>
// goods-item.js
show () {
// console.log('show')
if (this.timer || this.data.showGoods === true) return false
this.timer = setTimeout(() => {
this.setData({
showGoods: true,
})
}, 500)
},
hide () {
// console.log('hide')
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (this.data.showGoods) {
this.setData({
showGoods: false,
})
}
},
// 页面监听,控制goods显示隐藏
this.observer1 = wx.createIntersectionObserver(this, {
observeAll: true,
})
this.observer1
.relativeToViewport({
top: 200,
bottom: 400,
})
.observe('.goods-item', (res) => {
// console.log(res)
if (res.intersectionRatio > 0) {
this.selectComponent('#' + res.id).show()
} else if (res.intersectionRatio === 0) {
this.selectComponent('#' + res.id).hide()
}
})
一方面对页面滚动进行监听,一方面对商品的显示和隐藏做debounce处理,防止快速滚动页面时,商品不停地显示、隐藏操作dom。
# 内存优化
实际测试发现,对于存在大量商品的店铺(几百个),在滚动时还好,添加商品到购物车时依然卡顿。因为页面只渲染一个分类的商品(几到几十个),所以应该与渲染无关。单纯滚动页面不算卡,但是进行各种操作时就很明显,应该是内存的问题。 将获取全部分类和商品,改为获取全部分类,并且每次只请求一个分类的商品。这样,内存中的商品从几百个变成了几十个,明显流畅。 另外,连续点击添加购物车,小球动画很不流畅。需要对购物车操作进行优化。
# 添加购物车优化
在分类列表每个商品及购物车窗口每个商品中,都包含了一个加减购物车的组件。 每次添加购物车,都会调用添加购物车接口,然后重新请求购物车商品,重新计算左侧分类商品数量。这样在快速连续点击添加或移除商品时,会明显卡顿,小球动画尤其明显。移除商品同样。 在点击+或-按钮时,做一个debounce效果,立刻修改数量及动画,但是延迟提交请求。这样,在快速连续点击时,可以一次修改商品数量,而非多次请求。
// goods-counter.js
clearTimeout(this.countTimer)
this.setData({
tempCount: this.data.tempCount + 1
})
this.addbBall()
this.countTimer = setTimeout(() => {
this.$page().props.cartStore.editCartGoodsNum({
id: cartItem.cartId,
number: this.data.tempCount,
})
}, 200)
修改之后,小球动画非常流畅。
# 长列表优化2
之前的优化,当分类商品数量多时,还是能感觉到卡顿。 改用微信的长列表组件recycle-view来重构代码。recycle-view和scroll-view的写法差不多,但是针对长列表进行了优化。内部通过元素高度和滚动高度来计算哪些元素需要显示,其余的元素不渲染,通过padding来上下撑满高度。这样即使数据很多,页面只渲染固定数量的元素。
# 总结
通过这几方面的优化,终于实现了类似饿了么的流程效果。关键还是对于通过长列表组件来解决了大量数据的性能问题。