电商类微信小程序里,商品展示和搜索是用户接触最多的两个入口。一个用户打开小程序,先要看到商品,然后想找到自己想要的那件,整个过程快不快、顺不顺,直接影响成交。今天就用大白话聊一聊,这两个功能怎么优化,让用户用起来更舒服,也让你的小程序不那么卡。

一、为什么商品展示和搜索这么重要

先说一个场景。用户在地铁上打开你的小程序,信号不太好,如果列表加载半天不出图,他大概率直接退出。反过来,如果首页商品展示得清楚,图也加载得快,用户才有耐心往下翻。搜索更是如此。有的用户目标明确,进来就搜“连衣裙”,结果你搜索框半天没反应,或者搜出来的结果乱七八糟,他马上就去别家了。所以这两个功能不是小事,是小程序能不能留住人的关键。

二、商品展示的优化思路

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毫秒比较合适,太长了用户感觉迟钝,太短了请求还是很多。第四,搜索历史要记得去重,而且给用户一个清空入口,否则显得很烦。第五,前端排序只能用于本地已经有的数据,如果你的数据是一页一页从后端拿的,那排序必须由后端处理,否则你只能排序当前这一页,结果不对。

七、总结

商品展示和搜索优化,核心目标就是让用户少等、少点、少烦。少等说的是加载快,少点说的是搜索和筛选方便,少烦说的是不卡顿、不重复请求、有历史记录。本文说的方法都是很常见的套路:分页、懒加载、防抖、高亮、缓存历史。你在自己做小程序时,先分析自己的商品规模,再决定哪些优化放在前端,哪些交给后端。把基础功能做扎实,用户体验自然就上来了。

希望这篇白话讲解能让你少踩点坑。