long-list-batch.uvue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <template>
  2. <view style="flex: 1; background-color: aliceblue">
  3. <view class="tips">list-view组件虽然在渲染层有recycle机制,但长列表的vnode/Element太多也会造成初始化卡顿。
  4. 本示例有2000条数据,通过分批创建列表项,减少初始化卡顿。并通过闲时创建机制避免影响列表滚动等UI操作</view>
  5. <list-view style="flex: 1" :refresher-enabled="true" :refresher-triggered="refresherTriggered" @scroll="onScroll"
  6. @scrolltolower="onScrollToLower" @refresherrefresh="onRefresh" @refresherpulling="onRefresherPulling" @refresherrestore="onRefresherRestore">
  7. <list-item class="item" v-for="(item, index) in list" :key="index + '_' + item.id">
  8. <view><text>{{ item.name }}</text></view>
  9. <view><text style="font-size: 12px; color: #999999">{{
  10. item.info
  11. }}</text></view>
  12. </list-item>
  13. </list-view>
  14. </view>
  15. </template>
  16. <script lang="ts">
  17. type Item = {
  18. id : number;
  19. name : string;
  20. info : string;
  21. };
  22. class Jobs {
  23. private jobs : (() => Promise<void>)[] = [];
  24. paused : boolean = true;
  25. constructor() { }
  26. add(job : () => Promise<void>) {
  27. this.jobs.push(job);
  28. }
  29. pause() {
  30. this.paused = true;
  31. }
  32. private execute() {
  33. if (this.paused) {
  34. return;
  35. }
  36. if (this.jobs.length == 0) {
  37. this.paused = true
  38. return;
  39. }
  40. const job = this.jobs.shift();
  41. if (job != null) {
  42. job().then(() => {
  43. this.execute();
  44. });
  45. }
  46. }
  47. resume() {
  48. if(!this.paused) {
  49. return
  50. }
  51. this.paused = false;
  52. setTimeout(() => {
  53. this.execute();
  54. }, 0)
  55. }
  56. reset() {
  57. this.jobs = [];
  58. this.paused = true;
  59. }
  60. get done() : boolean {
  61. return this.jobs.length == 0;
  62. }
  63. }
  64. function delay(time : number) : Promise<void> {
  65. return new Promise(resolve => {
  66. setTimeout(() => {
  67. resolve()
  68. }, time)
  69. })
  70. }
  71. export default {
  72. data() {
  73. return {
  74. bigList: [] as Item[],
  75. list: [] as Item[],
  76. jobs: new Jobs(),
  77. batchSize: 100,
  78. scrolling: false,
  79. refresherTriggered: false,
  80. scrollendTimeout: -1,
  81. pulling: false,
  82. };
  83. },
  84. created() {
  85. // 模拟大列表数据
  86. for (let i = 0; i < 2000; i++) {
  87. this.bigList.push({
  88. id: i,
  89. name: `Wifi_` + i,
  90. info: `信号强度: -${Math.floor(Math.random() * 60) + 40
  91. } db, 安全性: WPA/WPA2/WPA3-Personal`,
  92. } as Item);
  93. }
  94. },
  95. onReady() {
  96. this.init();
  97. },
  98. methods: {
  99. init(autoResumeJobs: boolean = true) {
  100. // 将数据分批放入任务队列
  101. const batchCount = Math.ceil(this.bigList.length / this.batchSize);
  102. for (let i = 0; i < batchCount; i++) {
  103. const start = i * this.batchSize;
  104. const end = Math.min(start + this.batchSize, this.bigList.length);
  105. this.jobs.add(async () => {
  106. this.list.push(...this.bigList.slice(start, end));
  107. // 两批数据之间增加延迟,防止卡顿时间太久
  108. await this.$nextTick();
  109. await delay(100)
  110. });
  111. }
  112. if(autoResumeJobs) {
  113. this.jobs.resume();
  114. }
  115. },
  116. onScroll() {
  117. // 部分平台不支持scrollend事件,使用定时器模拟
  118. clearTimeout(this.scrollendTimeout)
  119. this.scrollendTimeout = setTimeout(() => {
  120. this.onScrollEnd()
  121. }, 100)
  122. if (this.scrolling) {
  123. return;
  124. }
  125. this.scrolling = true;
  126. // 滚动期间暂停分批加载,保证滚动流畅度
  127. this.jobs.pause();
  128. },
  129. onScrollEnd() {
  130. this.scrolling = false;
  131. // 滚动结束,继续执行分批加载逻辑
  132. this.jobs.resume();
  133. },
  134. onScrollToLower() {
  135. // 分批加载进行中,暂不执行滚动到底部加载更多数据的逻辑
  136. if (!this.jobs.done) {
  137. return;
  138. }
  139. // 加载更多数据
  140. },
  141. onRefresh() {
  142. // 下拉刷新触发,重置任务队列
  143. this.jobs.reset();
  144. this.refresherTriggered = true
  145. setTimeout(() => {
  146. this.refresherTriggered = false;
  147. this.list.splice(0, this.list.length);
  148. /**
  149. * refreshRestore事件会触发继续分批加载,此处init不自动调用resume。这样做能减少一些下拉刷新复位期间加载数据引发的卡顿。
  150. * TODO 清空列表也会导致下拉刷新复位时发生卡顿,后续再优化
  151. */
  152. this.init(false);
  153. }, 500)
  154. },
  155. onRefresherPulling() {
  156. if(this.pulling) {
  157. return
  158. }
  159. this.pulling = true
  160. // 在下拉刷新时暂停分批加载,避免影响刷新操作
  161. this.jobs.pause()
  162. },
  163. onRefresherRestore() {
  164. this.pulling = false
  165. // 下拉刷新结束后恢复分批加载
  166. this.jobs.resume();
  167. }
  168. },
  169. };
  170. </script>
  171. <style>
  172. .tips {
  173. margin: 10px;
  174. border-radius: 5px;
  175. padding: 10px;
  176. background-color: white;
  177. }
  178. .item {
  179. margin: 5px 10px;
  180. padding: 10px;
  181. border-radius: 5px;
  182. background-color: white;
  183. }
  184. </style>