@jetlinks-web/components 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,12 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.useVirtualScroll = useVirtualScroll;
7
7
  var _vue = require("vue");
8
8
  /**
9
- * 虚拟滚动 Hook
9
+ * 高性能虚拟滚动 Hook
10
+ * 优化点:
11
+ * 1. 使用累积高度缓存,避免重复计算
12
+ * 2. 使用二分查找快速定位索引
13
+ * 3. 使用 shallowRef 减少响应式开销
14
+ * 4. 滚动节流减少计算频率
10
15
  */
11
16
  function useVirtualScroll(options) {
12
17
  var itemCount = options.itemCount,
@@ -17,6 +22,9 @@ function useVirtualScroll(options) {
17
22
  overscan = _options$overscan === void 0 ? 5 : _options$overscan,
18
23
  _options$threshold = options.threshold,
19
24
  threshold = _options$threshold === void 0 ? 100 : _options$threshold;
25
+ // 固定行高模式(性能更高)
26
+ var isFixedHeight = typeof itemHeight === 'number';
27
+ var fixedItemHeight = isFixedHeight ? itemHeight : 54;
20
28
  // 容器高度(支持响应式)
21
29
  var containerHeight = (0, _vue.computed)(function () {
22
30
  if ((0, _vue.isRef)(containerHeightOption)) {
@@ -24,23 +32,30 @@ function useVirtualScroll(options) {
24
32
  }
25
33
  return containerHeightOption;
26
34
  });
27
- // 滚动位置
28
- var scrollTop = (0, _vue.ref)(0);
29
- // 动态行高缓存
30
- var itemHeights = (0, _vue.ref)(new Map());
35
+ // 滚动位置 - 使用 shallowRef 减少响应式开销
36
+ var scrollTop = (0, _vue.shallowRef)(0);
37
+ // 动态行高缓存(仅在非固定高度模式使用)
38
+ var itemHeights = (0, _vue.shallowRef)(new Map());
39
+ // 累积高度缓存 - 用于二分查找
40
+ var accumulatedHeights = (0, _vue.shallowRef)([]);
41
+ var heightsCacheValid = false;
31
42
  /**
32
43
  * 获取指定索引的行高
33
44
  */
34
45
  var getItemHeight = function getItemHeight(index) {
46
+ if (isFixedHeight) {
47
+ return fixedItemHeight;
48
+ }
35
49
  // 优先使用缓存的高度
36
- if (itemHeights.value.has(index)) {
37
- return itemHeights.value.get(index);
50
+ var cached = itemHeights.value.get(index);
51
+ if (cached !== undefined) {
52
+ return cached;
38
53
  }
39
- // 使用配置的高度
54
+ // 使用配置的高度函数
40
55
  if (typeof itemHeight === 'function') {
41
56
  return itemHeight(index);
42
57
  }
43
- return itemHeight;
58
+ return fixedItemHeight;
44
59
  };
45
60
  /**
46
61
  * 设置行高(用于动态测量)
@@ -48,65 +63,101 @@ function useVirtualScroll(options) {
48
63
  var setItemHeight = function setItemHeight(index, height) {
49
64
  if (height > 0 && itemHeights.value.get(index) !== height) {
50
65
  itemHeights.value.set(index, height);
66
+ heightsCacheValid = false; // 使缓存失效
67
+ }
68
+ };
69
+ /**
70
+ * 重建累积高度缓存
71
+ */
72
+ var rebuildAccumulatedHeights = function rebuildAccumulatedHeights() {
73
+ if (heightsCacheValid) return;
74
+ var count = itemCount.value;
75
+ var heights = new Array(count + 1);
76
+ heights[0] = 0;
77
+ if (isFixedHeight) {
78
+ // 固定高度模式,直接计算
79
+ for (var i = 0; i < count; i++) {
80
+ heights[i + 1] = heights[i] + fixedItemHeight;
81
+ }
82
+ } else {
83
+ // 动态高度模式
84
+ for (var _i = 0; _i < count; _i++) {
85
+ heights[_i + 1] = heights[_i] + getItemHeight(_i);
86
+ }
51
87
  }
88
+ accumulatedHeights.value = heights;
89
+ heightsCacheValid = true;
90
+ };
91
+ /**
92
+ * 二分查找:找到第一个累积高度大于 target 的索引
93
+ */
94
+ var binarySearchIndex = function binarySearchIndex(target) {
95
+ rebuildAccumulatedHeights();
96
+ var heights = accumulatedHeights.value;
97
+ if (heights.length === 0) return 0;
98
+ var left = 0;
99
+ var right = heights.length - 1;
100
+ while (left < right) {
101
+ var mid = Math.floor((left + right) / 2);
102
+ if (heights[mid] <= target) {
103
+ left = mid + 1;
104
+ } else {
105
+ right = mid;
106
+ }
107
+ }
108
+ return Math.max(0, left - 1);
109
+ };
110
+ /**
111
+ * 获取累积高度
112
+ */
113
+ var getAccumulatedHeight = function getAccumulatedHeight(index) {
114
+ rebuildAccumulatedHeights();
115
+ var heights = accumulatedHeights.value;
116
+ if (index < 0) return 0;
117
+ if (index >= heights.length) return heights[heights.length - 1] || 0;
118
+ return heights[index];
52
119
  };
53
120
  /**
54
121
  * 计算总高度
55
122
  */
56
123
  var totalHeight = (0, _vue.computed)(function () {
57
- var total = 0;
58
- for (var i = 0; i < itemCount.value; i++) {
59
- total += getItemHeight(i);
124
+ if (isFixedHeight) {
125
+ return itemCount.value * fixedItemHeight;
60
126
  }
61
- return total;
127
+ rebuildAccumulatedHeights();
128
+ var heights = accumulatedHeights.value;
129
+ return heights.length > 0 ? heights[heights.length - 1] : 0;
62
130
  });
63
131
  /**
64
- * 计算可视范围的起始索引
132
+ * 计算可视范围的起始索引 - 使用二分查找 O(log n)
65
133
  */
66
134
  var startIndex = (0, _vue.computed)(function () {
67
- if (itemCount.value < threshold) {
135
+ var count = itemCount.value;
136
+ if (count < threshold) {
68
137
  return 0;
69
138
  }
70
- var accumulated = 0;
71
- for (var i = 0; i < itemCount.value; i++) {
72
- var height = getItemHeight(i);
73
- if (accumulated + height > scrollTop.value) {
74
- return Math.max(0, i - overscan);
75
- }
76
- accumulated += height;
77
- }
78
- return 0;
139
+ var index = binarySearchIndex(scrollTop.value);
140
+ return Math.max(0, index - overscan);
79
141
  });
80
142
  /**
81
143
  * 计算可视范围的结束索引
82
144
  */
83
145
  var endIndex = (0, _vue.computed)(function () {
84
- if (itemCount.value < threshold) {
85
- return itemCount.value;
86
- }
87
- var start = startIndex.value;
88
- var accumulated = 0;
89
- // 从起始索引开始累加
90
- for (var i = 0; i < start; i++) {
91
- accumulated += getItemHeight(i);
92
- }
93
- for (var _i = start; _i < itemCount.value; _i++) {
94
- accumulated += getItemHeight(_i);
95
- if (accumulated > scrollTop.value + containerHeight.value) {
96
- return Math.min(itemCount.value, _i + 1 + overscan);
97
- }
146
+ var count = itemCount.value;
147
+ if (count < threshold) {
148
+ return count;
98
149
  }
99
- return itemCount.value;
150
+ var targetHeight = scrollTop.value + containerHeight.value;
151
+ var index = binarySearchIndex(targetHeight);
152
+ return Math.min(count, index + 1 + overscan);
100
153
  });
101
154
  /**
102
155
  * 顶部偏移量
103
156
  */
104
157
  var offsetY = (0, _vue.computed)(function () {
105
- var offset = 0;
106
- for (var i = 0; i < startIndex.value; i++) {
107
- offset += getItemHeight(i);
108
- }
109
- return offset;
158
+ var start = startIndex.value;
159
+ if (start === 0) return 0;
160
+ return getAccumulatedHeight(start);
110
161
  });
111
162
  /**
112
163
  * 可见行数
@@ -114,20 +165,29 @@ function useVirtualScroll(options) {
114
165
  var visibleCount = (0, _vue.computed)(function () {
115
166
  return endIndex.value - startIndex.value;
116
167
  });
168
+ // 滚动节流
169
+ var scrollRAF = null;
117
170
  /**
118
- * 处理滚动事件
171
+ * 处理滚动事件(带节流)
119
172
  */
120
173
  var handleScroll = function handleScroll(e) {
121
174
  var target = e.target;
122
- if (target) {
123
- scrollTop.value = target.scrollTop;
175
+ if (!target) return;
176
+ // 使用 RAF 节流
177
+ if (scrollRAF !== null) {
178
+ cancelAnimationFrame(scrollRAF);
124
179
  }
180
+ scrollRAF = requestAnimationFrame(function () {
181
+ scrollTop.value = target.scrollTop;
182
+ scrollRAF = null;
183
+ });
125
184
  };
126
185
  /**
127
186
  * 滚动到指定位置
128
187
  */
129
188
  var scrollTo = function scrollTo(offset) {
130
- scrollTop.value = Math.max(0, Math.min(offset, totalHeight.value - containerHeight.value));
189
+ var maxScroll = Math.max(0, totalHeight.value - containerHeight.value);
190
+ scrollTop.value = Math.max(0, Math.min(offset, maxScroll));
131
191
  };
132
192
  /**
133
193
  * 滚动到指定索引
@@ -136,14 +196,11 @@ function useVirtualScroll(options) {
136
196
  if (index < 0 || index >= itemCount.value) {
137
197
  return;
138
198
  }
139
- var offset = 0;
140
- for (var i = 0; i < index; i++) {
141
- offset += getItemHeight(i);
142
- }
143
- scrollTo(offset);
199
+ scrollTo(getAccumulatedHeight(index));
144
200
  };
145
- // 数据变化时重置滚动位置
201
+ // 数据变化时使缓存失效并重置滚动位置
146
202
  (0, _vue.watch)(itemCount, function () {
203
+ heightsCacheValid = false;
147
204
  var maxScroll = Math.max(0, totalHeight.value - containerHeight.value);
148
205
  if (scrollTop.value > maxScroll) {
149
206
  scrollTop.value = maxScroll;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetlinks-web/components",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -155,8 +155,8 @@
155
155
  "webpack-merge": "^5.0.0",
156
156
  "webpackbar": "^5.0.2",
157
157
  "@jetlinks-web/constants": "^1.0.9",
158
- "@jetlinks-web/utils": "^1.2.12",
159
- "@jetlinks-web/hooks": "^1.1.1"
158
+ "@jetlinks-web/hooks": "^1.1.1",
159
+ "@jetlinks-web/utils": "^1.2.12"
160
160
  },
161
161
  "publishConfig": {
162
162
  "registry": "https://registry.npmjs.org/",