Skip to content

Commit b6c1b3e

Browse files
committed
fix: tree render perf, close #5069
1 parent 4e70c6d commit b6c1b3e

File tree

1 file changed

+109
-77
lines changed

1 file changed

+109
-77
lines changed

components/vc-virtual-list/List.tsx

+109-77
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { PropType, Component, CSSProperties } from 'vue';
22
import {
3+
onMounted,
4+
onUpdated,
35
ref,
46
defineComponent,
57
watchEffect,
@@ -155,12 +157,58 @@ const List = defineComponent({
155157
null,
156158
);
157159

158-
const calRes = ref<{
160+
const calRes = reactive<{
159161
scrollHeight?: number;
160162
start?: number;
161163
end?: number;
162164
offset?: number;
163-
}>({});
165+
}>({
166+
scrollHeight: undefined,
167+
start: 0,
168+
end: 0,
169+
offset: undefined,
170+
});
171+
172+
const offsetHeight = ref(0);
173+
onMounted(() => {
174+
nextTick(() => {
175+
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
176+
});
177+
});
178+
onUpdated(() => {
179+
nextTick(() => {
180+
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
181+
});
182+
});
183+
watch(
184+
[useVirtual, mergedData],
185+
() => {
186+
if (!useVirtual.value) {
187+
Object.assign(calRes, {
188+
scrollHeight: undefined,
189+
start: 0,
190+
end: mergedData.value.length - 1,
191+
offset: undefined,
192+
});
193+
}
194+
},
195+
{ immediate: true },
196+
);
197+
watch(
198+
[useVirtual, mergedData, offsetHeight, inVirtual],
199+
() => {
200+
// Always use virtual scroll bar in avoid shaking
201+
if (useVirtual.value && !inVirtual.value) {
202+
Object.assign(calRes, {
203+
scrollHeight: offsetHeight.value,
204+
start: 0,
205+
end: mergedData.value.length - 1,
206+
offset: undefined,
207+
});
208+
}
209+
},
210+
{ immediate: true },
211+
);
164212
watch(
165213
[
166214
inVirtual,
@@ -170,82 +218,73 @@ const List = defineComponent({
170218
updatedMark,
171219
heights,
172220
() => props.height,
221+
offsetHeight,
173222
],
174223
() => {
175-
setTimeout(() => {
176-
if (!useVirtual.value) {
177-
calRes.value = {
178-
scrollHeight: undefined,
179-
start: 0,
180-
end: mergedData.value.length - 1,
181-
offset: undefined,
182-
};
183-
return;
184-
}
224+
if (!useVirtual.value || !inVirtual.value) {
225+
return;
226+
}
227+
if (!inVirtual.value) {
228+
Object.assign(calRes, {
229+
scrollHeight: offsetHeight.value,
230+
start: 0,
231+
end: mergedData.value.length - 1,
232+
offset: undefined,
233+
});
234+
return;
235+
}
185236

186-
// Always use virtual scroll bar in avoid shaking
187-
if (!inVirtual.value) {
188-
calRes.value = {
189-
scrollHeight: fillerInnerRef.value?.offsetHeight || 0,
190-
start: 0,
191-
end: mergedData.value.length - 1,
192-
offset: undefined,
193-
};
194-
return;
237+
let itemTop = 0;
238+
let startIndex: number | undefined;
239+
let startOffset: number | undefined;
240+
let endIndex: number | undefined;
241+
const dataLen = mergedData.value.length;
242+
const data = mergedData.value;
243+
for (let i = 0; i < dataLen; i += 1) {
244+
const item = data[i];
245+
const key = getKey(item);
246+
247+
const cacheHeight = heights.value[key];
248+
const currentItemBottom =
249+
itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight);
250+
251+
if (currentItemBottom >= state.scrollTop && startIndex === undefined) {
252+
startIndex = i;
253+
startOffset = itemTop;
195254
}
196255

197-
let itemTop = 0;
198-
let startIndex: number | undefined;
199-
let startOffset: number | undefined;
200-
let endIndex: number | undefined;
201-
const dataLen = mergedData.value.length;
202-
const data = mergedData.value;
203-
for (let i = 0; i < dataLen; i += 1) {
204-
const item = data[i];
205-
const key = getKey(item);
206-
207-
const cacheHeight = heights.value[key];
208-
const currentItemBottom =
209-
itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight);
210-
211-
if (currentItemBottom >= state.scrollTop && startIndex === undefined) {
212-
startIndex = i;
213-
startOffset = itemTop;
214-
}
215-
216-
// Check item bottom in the range. We will render additional one item for motion usage
217-
if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) {
218-
endIndex = i;
219-
}
220-
221-
itemTop = currentItemBottom;
256+
// Check item bottom in the range. We will render additional one item for motion usage
257+
if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) {
258+
endIndex = i;
222259
}
223260

224-
// Fallback to normal if not match. This code should never reach
225-
/* istanbul ignore next */
226-
if (startIndex === undefined) {
227-
startIndex = 0;
228-
startOffset = 0;
229-
}
230-
if (endIndex === undefined) {
231-
endIndex = dataLen - 1;
232-
}
261+
itemTop = currentItemBottom;
262+
}
263+
264+
// Fallback to normal if not match. This code should never reach
265+
/* istanbul ignore next */
266+
if (startIndex === undefined) {
267+
startIndex = 0;
268+
startOffset = 0;
269+
}
270+
if (endIndex === undefined) {
271+
endIndex = dataLen - 1;
272+
}
233273

234-
// Give cache to improve scroll experience
235-
endIndex = Math.min(endIndex + 1, dataLen);
236-
calRes.value = {
237-
scrollHeight: itemTop,
238-
start: startIndex,
239-
end: endIndex,
240-
offset: startOffset,
241-
};
274+
// Give cache to improve scroll experience
275+
endIndex = Math.min(endIndex + 1, dataLen);
276+
Object.assign(calRes, {
277+
scrollHeight: itemTop,
278+
start: startIndex,
279+
end: endIndex,
280+
offset: startOffset,
242281
});
243282
},
244-
{ immediate: true, flush: 'post' },
283+
{ immediate: true },
245284
);
246285

247286
// =============================== In Range ===============================
248-
const maxScrollHeight = computed(() => calRes.value.scrollHeight! - props.height!);
287+
const maxScrollHeight = computed(() => calRes.scrollHeight! - props.height!);
249288

250289
function keepInRange(newScrollTop: number) {
251290
let newTop = newScrollTop;
@@ -416,15 +455,6 @@ const List = defineComponent({
416455
setInstance,
417456
mergedData,
418457
} = this;
419-
const listChildren = renderChildren(
420-
mergedData,
421-
start,
422-
end,
423-
setInstance,
424-
children,
425-
sharedConfig,
426-
);
427-
428458
return (
429459
<div
430460
style={{
@@ -446,9 +476,11 @@ const List = defineComponent({
446476
offset={offset}
447477
onInnerResize={collectHeight}
448478
ref="fillerInnerRef"
449-
>
450-
{listChildren}
451-
</Filler>
479+
v-slots={{
480+
default: () =>
481+
renderChildren(mergedData, start, end, setInstance, children, sharedConfig),
482+
}}
483+
></Filler>
452484
</Component>
453485

454486
{useVirtual && (

0 commit comments

Comments
 (0)