温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
  • 忘记密码?
登录注册×
获取短信验证码
其他方式登录
点击 登录注册 即表示同意 《亿速云用户服务条款》
  • 服务器
  • 数据库
  • 开发技术
  • 网络安全
  • 互联网科技
登 录 注册有礼
最新更新 网站标签 地图导航
产品
  • 首页 > 
  • 教程 > 
  • 开发技术 > 
  • 小程序列表懒加载如何实现

小程序列表懒加载如何实现

发布时间:2022-04-02 09:14:16 来源:亿速云 阅读:499 作者:iii 栏目: 开发技术

本文小编为大家详细介绍“小程序列表懒加载如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“小程序列表懒加载如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    小程序上的列表懒加载

    长列表我们经常接触到,长列表为什么需要懒加载呢,因为一旦渲染内容多了,渲染引擎就需要更多的时间去渲染画面,这时可能会出现页面白屏、卡顿等。而用户其实只需要看到视窗内的内容就可以了,不用一次性把全部内容渲染完,所以可以通过懒加载实现。

    分页加载

    常见的列表懒加载是和后端一起实现,也就是分页加载。前端请求第几页的数据,后端就返回第几页的数据。前端要实现的交互就是当用户滑动到页面底部时,就要请求下一页的数据。

    用scroll事件监听

    小程序列表懒加载如何实现

    高度示意图

    监听scroll事件,事件回调会有当前元素的滚动距离scrollTop,当scrollTop+screenHeight等于滚动高度scrollHeight时,表示已经滑动到底部。

    在小程序中,Page对象提供onReachBottomapi

    onReachBottom: function() {
      // 页面触底时执行
    },

    用IntersectionObserver监听

    用滚动监听会非常耗性能,滚动时频繁触发回调的,所以会不断去计算判断。比较好的优化方案是IntersectionObserverAPI,当IntersectionObserver监听的元素与可视区有相交状态时,就会产生回调,这样就减少了触发的频率

    Page({
      onLoad: function(){
        wx.createIntersectionObserver().relativeToViewport({bottom: 100}).observe('.target-class', (res) => {
          res.intersectionRatio // 相交区域占目标节点的布局区域的比例,不等于0时表示有相交
          res.intersectionRect // 相交区域
          res.intersectionRect.left // 相交区域的左边界坐标
          res.intersectionRect.top // 相交区域的上边界坐标
          res.intersectionRect.width // 相交区域的宽度
          res.intersectionRect.height // 相交区域的高度
        })
      }
    })

    前端分页渲染

    上面说的都是前端结合接口的分页加载。假如说接口没有分页,直接就返回了庞大的数据列表。前端如果直接就setData所有数据,会渲染很久,其实复杂的列表渲染20条的时候就已经很慢了。这个时候需要对已经获取到的数据进行分页,分批进行渲染。

    小程序列表懒加载如何实现

    通过右侧面板可以看到,一开始没有渲染所有节点,在滑动页面过程中节点再不断增加。

    直接上代码

    <!-- pages/first/index.wxml -->
    <view class="container">
      <block wx:for="{{goodsList}}" wx:key="index" wx:for-item="subItemList">
        <block wx:for="{{subItemList}}" wx:key="name">
          <view class="item">{{item.name}}</view>
        </block>
      </block>
    </view>

    goodsList是一个二维数组,用wx:for双重遍历

    // pages/first/index.js
    const list = Array.from({length: 5}, (item, index) => {
      return Array.from({length: Math.ceil(Math.random()*10 + 5)}, (subItem, subIndex) => {
        return {name: `第${index+1}屏, 第${subIndex+1}个`}
      })
    })
    /**
    生成的list是一个二维数组
    [
      [{}, {}],
      [{}, {}],
      [{}, {}],
      ...
    ]
    **/
    
    Page({
      data: {
        goodsList: [],
        lazyloadIdx: 0
      },
      onLoad() {
        this.setData({
          goodsList: [list[0]],
          lazyloadIdx: 1
        })
      },
      // 滑动到底部时往goodsList添加数据
      onReachBottom () {
        console.log('onReachBottom');
        let { lazyloadIdx } = this.data
        if (lazyloadIdx >= list.length) return
        this.setData({
          [`goodsList[${lazyloadIdx}]`]: list[lazyloadIdx],
          lazyloadIdx: lazyloadIdx+1
        })
      }
    })

    每次只setData一屏数据,既减少了setData数据量,又减少渲染时间

    用IntersectionObserver代替onReachBottom

    有很多场景用不了onReachBottom,那我们只能用IntersectionObserver代替。优化一下上面的代码

    # pages/second/index.wxml
    <view class="container">
      <block wx:for="{{goodsList}}" wx:key="index" wx:for-item="subItemList">
        <block wx:for="{{subItemList}}" wx:key="name">
          <view class="item">{{item.name}}</view>
        </block>
      </block>
    +  <view id="observer" class="bottom"></view>
    </view>

    增加节点用来监听

    //  pages/second/index.js
    let lazyloadOb = null
    Page({
      data: {
        goodsList: [],
        lazyloadIdx: 0
      },
      onLoad() {
        this.setData({
          goodsList: [list[0]],
          lazyloadIdx: 1
        })
        this.initObserver()
      },
      onunload () {
        this.disconnenct()
      },
      lazyloadNext () {
        console.log('lazyloadNext');
        let { lazyloadIdx } = this.data
        if (lazyloadIdx >= list.length) return
        this.setData({
          [`goodsList[${lazyloadIdx}]`]: list[lazyloadIdx],
          lazyloadIdx: lazyloadIdx+1
        })
      },
      initObserver () {
        lazyloadOb = wx.createIntersectionObserver().relativeToViewport({ bottom: 50 }).observe('#observer', (res) => {
          console.log('res.intersectionRatio', res.intersectionRatio);
          // 触发回调时加载下一屏
          if (res.intersectionRatio) this.lazyloadNext()
        })
      },
      disconnenct() {
        lazyloadOb.disconnenct()
      }
    })

    加需求!

    后端返回的商品列表只是一个一维数组,需要前端转为二维数组,现在需要每屏的列表长度为5。

    假设商品列表个数为21,那么会生成二维数组对应的子项长度:

    // 伪代码
    [5, 5, 5, 5, 1]

    我们可以设定分页大小pageSize为5,当前分页pageNum,然后list.slice(pageNum, pageSize)就能截取对应的数据,再加入到二维数组中。

    但是产品来加需求了,商品列表默认只展示非售罄商品+一个售罄商品,其余售罄商品要点击【查看更多】按钮才展示

    假设非售罄商品有16个,售罄11个,每屏的列表长度还是5,那么:

    [
      5, 5, 5,    // 非售罄
      2,          // 非售罄+售罄
      5, 5        // 售罄
    ]

    只有两个的长度不大适合再分一屏,所以小于5时,直接跟前面的合并

    [
      5, 5, 7, // 非售罄+一个售罄
      5, 5     // 售罄
    ]

    这个时候设定pageSize就没法满足了,所以要根据售罄个数,非售罄个数以及一屏长度,算出长度数组,再算出对应的二维数组

    /**
      * @desc 生成商品列表的子项长度
      * 展示列表包含售罄的,在非售罄列表最后拼接一个售罄商品,点击展开再加载售罄
      * 
      * @param {number} onSaleLen 非售罄长度
      * @param {number} soldOutLen 售罄长度
      * @returns { { subSize: Array<number>; soldOutLen: number } }
      */
    genSubListSize (onSaleLen, soldOutLen) {
      // 有售罄的时候,放一项售罄到非售罄那边去
      if (soldOutLen) {
        soldOutLen-= 1
        onSaleLen+=1
      }
      const arr = []
      const lazyloadListPartLength = 5 // 一屏5个
      let firstSize = 0
      if (onSaleLen < lazyloadListPartLength*2) {
        firstSize = onSaleLen
        onSaleLen = 0
      } else {
        firstSize = lazyloadListPartLength
        onSaleLen -= lazyloadListPartLength
      }
      arr.push(firstSize)
      
      // 子项长度
      const size = lazyloadListPartLength
      const remainder = onSaleLen % size
      arr.push(
        ...Array.from({length: Math.floor(onSaleLen/size) - 1}, () => size),
      )
      if (onSaleLen) {
        arr.push(onSaleLen <= size ? onSaleLen : size + remainder)
      }
      // 记录此时售罄项的索引,因为要点击展开才能加载售罄列表
      const soldOutIndex = arr.length
      if (soldOutLen) {
        const remainder = soldOutLen % size
        arr.push(
          ...Array.from({length: Math.floor(soldOutLen/size) - 1}, () => size), 
          soldOutLen <= size ? soldOutLen : size + remainder
        )
      }
    
      console.log('genSubListSize', arr)
      
      return {
        subSize: arr,
        soldOutLen,
        soldOutIndex
      }
    }
    /**
      * eg: onSaleLen = 25; soldOutLen = 9; size = 5
      * return [5, 5, 5, 5, 6, 8]
      * eg: onSaleLen = 15; soldOutLen = 9; size = 5
      * return [5, 5, 6, 8]
      * eg: onSaleLen = 10; soldOutLen = 10; size = 5
      * return [5, 6, 9]
      * eg: onSaleLen = 14; soldOutLen = 10; size = 5
      * return [5, 5, 5, 9]
      * eg: onSaleLen = 8; soldOutLen = 9; size = 5
      * return [9, 8]
      * eg: onSaleLen = 2; soldOutLen = 10; size = 7 像这种小于非售罄小于size的,只能取到3了
      * return [3, 9]
    **/

    现在取列表长度为20,12个非售罄,8个售罄,一屏5个

    // pages/third/index
    const goodsList = Array.from({length: 20}, (item, index) => {
      return {name: `第${index+1}个`, soldOut: index < 12 ? false : true}
    })
    Page({
      // ...
      onLoad() {
        this.initObserver()
        this.handleGoodsList()
      },
      handleGoodsList () {
        const { onSaleLen, soldOutLen } = this.countSaleLen()
        console.log('onSaleLen', onSaleLen, 'soldOutLen', soldOutLen);
        const {
          subSize,
          soldOutLen: soldOutLength,
          soldOutIndex
        } = this.genSubListSize(onSaleLen, soldOutLen)
        const renderList = this.genRenderList(subSize)
        console.log('renderList', renderList);
      },
      countSaleLen () {
        const soldOutIndex = goodsList.findIndex(good => good.soldOut)
        if (soldOutIndex === -1) {
          return {
            onSaleLen: goodsList.length,
            soldOutLen: 0
          }
        }
        return {
          onSaleLen: soldOutIndex,
          soldOutLen: goodsList.length - soldOutIndex
        }
      },
      // 根据分组数组生成渲染列表
      genRenderList (subSize) {
        const before = goodsList
        const after = []
        let subList = [] // 二维数组子项
        let subLen = 0 // 子项长度
        let splitSizeArrIdx = 0 // 长度数组索引
        for (let i = 0; i < before.length; i++) {
          if (subLen === subSize[splitSizeArrIdx]) {
            splitSizeArrIdx++
            after.push(subList)
            subList = []
            subLen = 0
          }
          before[i]['part'] = `第${splitSizeArrIdx+1}屏`
          subList.push(before[i])
          subLen++
        }
        // 最后一个子项添加进去
        after.push(subList)
        return after
      }
    })

    打印一下renderList,得到了动态切分的数据了

    小程序列表懒加载如何实现

    列表分组

    跑一下demo

    小程序列表懒加载如何实现

    当然需求是不断变化的,下次就不一定是售罄非售罄了,但是万变不离其宗,再怎么分,把每一项的数组长度定好之后,再生成渲染的renderList就可以了。所以可以把懒加载的这块逻辑抽离出来封装。

    读到这里,这篇“小程序列表懒加载如何实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

    向AI问一下细节
    推荐阅读:
    1. 小程序如何实现搜索界面 小程序实现推荐搜索列表效果
    2. 小程序实现列表点赞功能

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    小程序
    • 上一篇新闻:
      navicat连接Ubuntu虚拟机的mysql操作怎么实现
    • 下一篇新闻:
      Web端测试PHP代码函数覆盖率的解决方法

    猜你喜欢

    • 云服务器io值是什么意思
    • 为什么无法远程连接香港服务器
    • 香港服务器和新加坡服务器怎么选
    • 香港服务器与美国服务器有什么区别
    • 香港服务器被墙了怎么办
    • 哪些因素会影响香港服务器上网速度
    • 如何连接到香港服务器
    • 香港服务器建站有什么优势
    • 香港服务器怎么提高访问速度
    • 香港云服务器和国内云服务器有什么区别
    最新资讯
    • Clojure在物联网领域的应用有哪些
    • 如何在Clojure中使用GraphQL
    • Clojure中有哪些加解密和安全相关的库
    • 如何在Clojure中实现国际化和本地化
    • 介绍Clojure的testing frameworks和库
    • Clojure在大数据处理领域的应用情况如何
    • 如何使用Clojure进行分布式计算
    • 解释Clojure中向量、列表、集合和映射的区别和用途
    • Clojure中的逻辑编程和规则引擎用法是什么
    • 如何在Clojure中处理XML和HTML数据
    相关推荐
    • 小程序实现列表删除功能
    • 小程序如何实现图片懒加载方式
    • 如何在微信小程序中实现图片懒加载
    • 小程序中怎么实现图片懒加载
    • 小程序中怎么实现列表渲染
    • 小程序如何实现城市列表的选择
    • 微信小程序如何实现虚拟列表
    • 微信小程序图片懒加载如何实现
    • 微信小程序如何实现城市列表
    • 小程序如何实现分页查询列表

    相关标签

    微信小程序 小程序开发 附近的小程序 微信小程序开发 小程序源码 支付宝小程序 小程序云开发 门店小程序 电商小程序 转小程序 商城小程序 微信小程序商城 微信小程序开发框架 python小程序 百度小程序 小程序码 小程序跳转 columnstore 元组 前端模板
    AI

    深圳SEO优化公司柳州优化报价荆门seo排名多少钱哈密推广网站哪家好龙岩百度网站优化价格海北百姓网标王报价邵阳网站优化按天收费价格坑梓网站制作多少钱阳泉网站推广工具推荐天津外贸网站设计多少钱天津SEO按效果付费思茅网站推广推荐民治网页制作推荐大连SEO按天计费价格邵阳SEO按天扣费价格铜陵外贸网站制作永湖企业网站设计价格伊春seo优化公司苏州网站优化按天扣费推荐鄂州百姓网标王推广公司湖州网站设计哪家好张北百姓网标王哪家好沙井网站优化按天扣费濮阳关键词排名报价江门网站优化按天收费推荐伊春如何制作网站哪家好许昌SEO按天扣费公司邢台英文网站建设哪家好红河如何制作网站价格淄博模板网站建设哪家好湘潭网站制作公司歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

    深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化