电商类微信小程序里,商品展示和搜索是用户接触最多的两个入口。一个用户打开小程序,先要看到商品,然后想找到自己想要的那件,整个过程快不快、顺不顺,直接影响成交。今天就用大白话聊一聊,这两个功能怎么优化,让用户用起来更舒服,也让你的小程序不那么卡。
一、为什么商品展示和搜索这么重要
先说一个场景。用户在地铁上打开你的小程序,信号不太好,如果列表加载半天不出图,他大概率直接退出。反过来,如果首页商品展示得清楚,图也加载得快,用户才有耐心往下翻。搜索更是如此。有的用户目标明确,进来就搜“连衣裙”,结果你搜索框半天没反应,或者搜出来的结果乱七八糟,他马上就去别家了。所以这两个功能不是小事,是小程序能不能留住人的关键。
二、商品展示的优化思路
2.1 别一次把全部商品塞给用户
很多新手会把几百个商品一次性放到页面上,结果页面变量越来越大,内存蹭蹭涨,滑动起来特别卡。正确做法是分批加载,也就是常说分页。一次只展示一页,比如20个,用户滑到底部再加载下一批。就好比吃饭,一口一口吃,别一桌子菜全塞嘴里。
示例:分页加载的js
技术栈:微信小程序原生 JavaScript
// pages/goods/goods.js
Page({
data: {
goodsList: [], // 当前展示的商品列表
pageNum: 1, // 当前第几页
pageSize: 20, // 每页显示多少个
hasMore: true, // 是否还有下一页
loading: false // 防止重复请求
},
onLoad() {
this.loadGoods(); // 页面加载后先拉第一页数据
},
loadGoods() {
if (this.data.loading || !this.data.hasMore) return; // 正在加载或者没数据了就停
this.setData({ loading: true }); // 标记正在加载,免得用户疯狂下拉
// 模拟请求商品数据,实际开发中换成 wx.request 调用后端接口
const newList = this.getMockGoods(this.data.pageNum, this.data.pageSize);
this.setData({
goodsList: this.data.goodsList.concat(newList), // 把新数据拼到老数据后面
pageNum: this.data.pageNum + 1, // 页码加一
loading: false,
hasMore: newList.length > 0 // 如果返回空,说明没有更多了
});
},
getMockGoods(pageNum, pageSize) {
// 这里只是造数据,真实项目会从服务端返回
const arr = [];
for (let i = 0; i < pageSize; i++) {
arr.push({
id: (pageNum - 1) * pageSize + i,
name: '商品' + ((pageNum - 1) * pageSize + i),
price: Math.floor(Math.random() * 1000) / 10
});
}
return arr;
},
onReachBottom() {
this.loadGoods(); // 微信小程序触底事件,用户滚到底部时自动加载下一页
}
});
这段代码里,每次只拉20条,“hasMore”用来判断后面还有没有。触底时再加载,用户几乎感觉不到卡。
2.2 图片加载的讲究
图片是电商的命根子。图片太大,加载起来特别慢;图片太多,内存也被占满。小程序里有一个很简单的优化方式:图片用懒加载。在wxml的image标签上加一个 lazy-load 属性,图片没进入屏幕就不加载。另外,不同位置用不同尺寸的图,列表页用缩略图,详情页再用大图,不要一张原图到处用。
还有一点,别在JS里去拼图片链接,而是让后端把图片处理成合适的尺寸返回。比如你请求接口的时候带上图片宽度,后端返回对应宽度的图,这张图体积能小不少。
2.3 排序和筛选别让用户等
用户界面上的排序,比如按销量、按价格、按上新时间,每次切换都重新请求服务器,体验不好。小数据量的时候,可以前端直接排序,瞬间完成。大数据量则服务端排好再返回。下面给一个前端排序的示例,注意要把价格转成数字再比。
技术栈:微信小程序原生 JavaScript
// pages/goods/goods.js
Page({
data: {
goodsList: [], // 商品列表
sortType: 'default' // 当前排序方式:default-默认 price-价格 sales-销量
},
// 切换排序方式
changeSort(e) {
const type = e.currentTarget.dataset.type; // 从按钮上取得想要排序的方式
const list = this.data.goodsList.slice(); // 拷贝一份,不要直接改原数组
if (type === 'price') {
list.sort((a, b) => a.price - b.price); // 价格从低到高
} else if (type === 'sales') {
list.sort((a, b) => b.sales - a.sales); // 销量从高到低
}
this.setData({
goodsList: list,
sortType: type
});
}
});
注意,这种前端排序适合数据量较小的场景。如果你的商品有几万条,还是让后端来做,前端只管发请求,不然会把用户的手机卡死。
三、搜索功能优化
3.1 防抖:别让用户每敲一个字都请求
很多小程序的搜索框,用户刚输一个字母,就发一次请求。如果用户输入“连衣裙”,相当于发了三个请求,浪费流量,还容易出现旧请求覆盖新请求的情况。优化办法是防抖:用户在停止输入后过几百毫秒,再去请求。就像一个自动门,人一直站在那里不动,过一会儿才关门。
技术栈:微信小程序原生 JavaScript
// pages/goods/goods.js
Page({
data: {
keyword: '', // 搜索关键词
searchTimer: null // 计时器id
},
// 输入框内容变化时触发
onSearchInput(e) {
const value = e.detail.value;
this.setData({ keyword: value });
// 如果上一次的定时器还没走完,先把它清掉
if (this.data.searchTimer) {
clearTimeout(this.data.searchTimer);
}
// 400毫秒后真正执行搜索
const timer = setTimeout(() => {
this.doSearch(value);
}, 400);
this.setData({ searchTimer: timer });
},
doSearch(keyword) {
// 这里执行搜索请求,或者结合本地过滤来做
console.log('搜索关键词:', keyword);
}
});
这个防抖函数看起来简单,却能让请求数量减少一大半。
3.2 关键词匹配与高亮
搜索出来之后,让用户一眼看到命中在哪。比如用户搜“红色”,商品名称里“红色”这两个字要变色。这需要后端返回或前端处理。前端高亮的思路是,把名称按关键词拆开,再拼上高亮样式。
技术栈:微信小程序原生 JavaScript
// 假设从后端拿到的商品名称是:红色连衣裙
// 关键词:红色
// 我们想把“红色”替换成带标记的样式,比如 <text class="highlight">红色</text>
function highlightName(name, keyword) {
if (!keyword) return name; // 没有关键词就原样返回
const lowerName = name.toLowerCase(); // 不区分大小写,先转小写
const lowerKeyword = keyword.toLowerCase();
const index = lowerName.indexOf(lowerKeyword); // 找关键词第一次出现的位置
if (index === -1) return name; // 没有匹配就原样返回
// 把关键词前后的部分拆开,中间拼上高亮标签
return name.substring(0, index) +
'<text class="highlight">' + name.substring(index, index + keyword.length) + '</text>' +
name.substring(index + keyword.length);
}
在WXML里,用 rich-text 组件渲染这个字符串,就能看到高亮效果。注意,这里只是为了演示,真实项目还要考虑大小写转换后的位置和原始字符串的对应关系。
3.3 搜索历史与推荐
搜索历史放在本地,可以省去用户重复输入的麻烦。推荐词则可以引导用户去搜热门商品。推荐词一般是后端根据大数据算出来的,前端只需要展示。本地历史用 storage 保存,很简单,给大家看一个存历史的例子。
技术栈:微信小程序原生 JavaScript
// pages/goods/goods.js
const HISTORY_KEY = 'search_history'; // 存到本地的key
Page({
data: {
history: [] // 历史关键词列表
},
onLoad() {
// 页面加载时从本地缓存里把历史读出来
const history = wx.getStorageSync(HISTORY_KEY) || [];
this.setData({ history: history });
},
// 用户点击搜索按钮时保存关键词
saveHistory(keyword) {
if (!keyword) return;
let history = wx.getStorageSync(HISTORY_KEY) || [];
// 把已有列表里相同的关键词删掉,保证最新的关键词排在前面
history = history.filter(item => item !== keyword);
history.unshift(keyword); // 插到最前面
if (history.length > 10) { // 最多保留10个
history.pop();
}
wx.setStorageSync(HISTORY_KEY, history); // 写回本地
this.setData({ history: history });
},
// 清空历史
clearHistory() {
wx.removeStorageSync(HISTORY_KEY);
this.setData({ history: [] });
}
});
这样用户每次打开小程序,历史搜索还在,他觉得这个小程序真懂我。
四、一个完整的实战示例
把前面的东西合到一起,写一个简单的商品搜索页面逻辑。技术栈:微信小程序原生 JavaScript。这里模拟了商品列表、搜索分页、排序、高亮、历史记录,你可以直接抄到项目里,再根据后端接口调整。
// pages/goods/goods.js
const HISTORY_KEY = 'search_history';
Page({
data: {
goodsList: [], // 全部商品(模拟数据)
showList: [], // 搜索和排序后要展示的商品
keyword: '', // 搜索关键词
sortType: 'default', // 排序方式
history: [] // 搜索历史
},
onLoad() {
// 启动时初始化商品数据,实际项目中用 wx.request 请求服务器
this.setData({
goodsList: this.getMockGoods(),
showList: this.getMockGoods(),
history: wx.getStorageSync(HISTORY_KEY) || []
});
},
// 造一批假数据
getMockGoods() {
const goods = [];
const names = ['红色连衣裙', '蓝色牛仔裤', '白色T恤', '黑色运动鞋', '红色卫衣', '蓝色帽子'];
for (let i = 0; i < 20; i++) {
const name = names[i % names.length] + (i + 1);
const price = Math.floor(Math.random() * 500 + 50);
const sales = Math.floor(Math.random() * 1000);
goods.push({ id: i, name: name, price: price, sales: sales });
}
return goods;
},
// 搜索输入框实时触发(防抖)
onSearchInput(e) {
const value = e.detail.value;
this.setData({ keyword: value });
if (this._timer) {
clearTimeout(this._timer);
}
this._timer = setTimeout(() => {
this.filterGoods(value);
}, 300);
},
// 根据关键词过滤商品
filterGoods(keyword) {
let list = this.data.goodsList;
if (keyword) {
list = list.filter(item => item.name.indexOf(keyword) !== -1);
}
// 如果排序状态不是默认,再排一下
list = this.sortList(list, this.data.sortType);
this.setData({ showList: list });
},
// 切换排序
changeSort(e) {
const type = e.currentTarget.dataset.type;
const list = this.sortList(this.data.showList, type);
this.setData({ sortType: type, showList: list });
},
// 排序逻辑抽出来复用
sortList(list, type) {
const arr = list.slice();
if (type === 'price') {
arr.sort((a, b) => a.price - b.price);
} else if (type === 'sales') {
arr.sort((a, b) => b.sales - a.sales);
}
return arr;
},
// 点击搜索按钮时保存历史
onSearchConfirm(e) {
const keyword = e.detail.value;
this.saveHistory(keyword);
},
// 保存历史
saveHistory(keyword) {
if (!keyword) return;
let history = wx.getStorageSync(HISTORY_KEY) || [];
history = history.filter(item => item !== keyword);
history.unshift(keyword);
if (history.length > 10) history.pop();
wx.setStorageSync(HISTORY_KEY, history);
this.setData({ history: history });
},
// 清空历史
clearHistory() {
wx.removeStorageSync(HISTORY_KEY);
this.setData({ history: [] });
},
// 用户点击历史关键词
selectHistory(e) {
const keyword = e.currentTarget.dataset.keyword;
this.setData({ keyword: keyword });
this.filterGoods(keyword);
}
});
这个小示例覆盖了搜索、排序、历史。你在实际项目里可以在这个基础上扩展。注意,这里没有做分页,就是为了让你看清核心逻辑。真实项目请务必加上分页,不然商品多了一样会卡。
五、应用场景与优缺点
这套优化方案适合什么场景呢?适合商品数量在几千以内、后端接口不复杂的电商小程序。优点是代码简单,前端就能搞定搜索和排序,用户体验也流畅。缺点是数据量大了之后,前端过滤效率很低,而且每次搜索要把全量数据拉到本地,非常耗流量和内存。这时候就需要后端配合,用接口做搜索和排序,前端只负责发送关键词和接收结果。
另外,防抖和高亮这类优化,不管数据量大小都适用,是纯前端的收益。后端搜索则要考虑分词、索引,比如用户搜“红裙”,能不能匹配到“红色连衣裙”,这个前端做不到,需要后端支持。
六、注意事项
第一,分页时千万要防止重复请求,不然用户滑到底部会同时加载好几页,页面数据混乱。上面代码里的 loading 锁就是干这个的。第二,图片懒加载要配合合理的占位图,不然图片没加载出来时,页面会跳来跳去。第三,搜索框的防抖时间别太长,300到400毫秒比较合适,太长了用户感觉迟钝,太短了请求还是很多。第四,搜索历史要记得去重,而且给用户一个清空入口,否则显得很烦。第五,前端排序只能用于本地已经有的数据,如果你的数据是一页一页从后端拿的,那排序必须由后端处理,否则你只能排序当前这一页,结果不对。
七、总结
商品展示和搜索优化,核心目标就是让用户少等、少点、少烦。少等说的是加载快,少点说的是搜索和筛选方便,少烦说的是不卡顿、不重复请求、有历史记录。本文说的方法都是很常见的套路:分页、懒加载、防抖、高亮、缓存历史。你在自己做小程序时,先分析自己的商品规模,再决定哪些优化放在前端,哪些交给后端。把基础功能做扎实,用户体验自然就上来了。
希望这篇白话讲解能让你少踩点坑。
Comments