fix: tree render perf, close #5069
parent
4e70c6dd77
commit
b6c1b3ed86
|
@ -1,5 +1,7 @@
|
||||||
import type { PropType, Component, CSSProperties } from 'vue';
|
import type { PropType, Component, CSSProperties } from 'vue';
|
||||||
import {
|
import {
|
||||||
|
onMounted,
|
||||||
|
onUpdated,
|
||||||
ref,
|
ref,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
|
@ -155,12 +157,58 @@ const List = defineComponent({
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const calRes = ref<{
|
const calRes = reactive<{
|
||||||
scrollHeight?: number;
|
scrollHeight?: number;
|
||||||
start?: number;
|
start?: number;
|
||||||
end?: number;
|
end?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
}>({});
|
}>({
|
||||||
|
scrollHeight: undefined,
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
offset: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const offsetHeight = ref(0);
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onUpdated(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
offsetHeight.value = fillerInnerRef.value?.offsetHeight || 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
[useVirtual, mergedData],
|
||||||
|
() => {
|
||||||
|
if (!useVirtual.value) {
|
||||||
|
Object.assign(calRes, {
|
||||||
|
scrollHeight: undefined,
|
||||||
|
start: 0,
|
||||||
|
end: mergedData.value.length - 1,
|
||||||
|
offset: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
[useVirtual, mergedData, offsetHeight, inVirtual],
|
||||||
|
() => {
|
||||||
|
// Always use virtual scroll bar in avoid shaking
|
||||||
|
if (useVirtual.value && !inVirtual.value) {
|
||||||
|
Object.assign(calRes, {
|
||||||
|
scrollHeight: offsetHeight.value,
|
||||||
|
start: 0,
|
||||||
|
end: mergedData.value.length - 1,
|
||||||
|
offset: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
inVirtual,
|
inVirtual,
|
||||||
|
@ -170,82 +218,73 @@ const List = defineComponent({
|
||||||
updatedMark,
|
updatedMark,
|
||||||
heights,
|
heights,
|
||||||
() => props.height,
|
() => props.height,
|
||||||
|
offsetHeight,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
setTimeout(() => {
|
if (!useVirtual.value || !inVirtual.value) {
|
||||||
if (!useVirtual.value) {
|
return;
|
||||||
calRes.value = {
|
}
|
||||||
scrollHeight: undefined,
|
if (!inVirtual.value) {
|
||||||
start: 0,
|
Object.assign(calRes, {
|
||||||
end: mergedData.value.length - 1,
|
scrollHeight: offsetHeight.value,
|
||||||
offset: undefined,
|
start: 0,
|
||||||
};
|
end: mergedData.value.length - 1,
|
||||||
return;
|
offset: undefined,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemTop = 0;
|
||||||
|
let startIndex: number | undefined;
|
||||||
|
let startOffset: number | undefined;
|
||||||
|
let endIndex: number | undefined;
|
||||||
|
const dataLen = mergedData.value.length;
|
||||||
|
const data = mergedData.value;
|
||||||
|
for (let i = 0; i < dataLen; i += 1) {
|
||||||
|
const item = data[i];
|
||||||
|
const key = getKey(item);
|
||||||
|
|
||||||
|
const cacheHeight = heights.value[key];
|
||||||
|
const currentItemBottom =
|
||||||
|
itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight);
|
||||||
|
|
||||||
|
if (currentItemBottom >= state.scrollTop && startIndex === undefined) {
|
||||||
|
startIndex = i;
|
||||||
|
startOffset = itemTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always use virtual scroll bar in avoid shaking
|
// Check item bottom in the range. We will render additional one item for motion usage
|
||||||
if (!inVirtual.value) {
|
if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) {
|
||||||
calRes.value = {
|
endIndex = i;
|
||||||
scrollHeight: fillerInnerRef.value?.offsetHeight || 0,
|
|
||||||
start: 0,
|
|
||||||
end: mergedData.value.length - 1,
|
|
||||||
offset: undefined,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemTop = 0;
|
itemTop = currentItemBottom;
|
||||||
let startIndex: number | undefined;
|
}
|
||||||
let startOffset: number | undefined;
|
|
||||||
let endIndex: number | undefined;
|
|
||||||
const dataLen = mergedData.value.length;
|
|
||||||
const data = mergedData.value;
|
|
||||||
for (let i = 0; i < dataLen; i += 1) {
|
|
||||||
const item = data[i];
|
|
||||||
const key = getKey(item);
|
|
||||||
|
|
||||||
const cacheHeight = heights.value[key];
|
// Fallback to normal if not match. This code should never reach
|
||||||
const currentItemBottom =
|
/* istanbul ignore next */
|
||||||
itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight);
|
if (startIndex === undefined) {
|
||||||
|
startIndex = 0;
|
||||||
|
startOffset = 0;
|
||||||
|
}
|
||||||
|
if (endIndex === undefined) {
|
||||||
|
endIndex = dataLen - 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentItemBottom >= state.scrollTop && startIndex === undefined) {
|
// Give cache to improve scroll experience
|
||||||
startIndex = i;
|
endIndex = Math.min(endIndex + 1, dataLen);
|
||||||
startOffset = itemTop;
|
Object.assign(calRes, {
|
||||||
}
|
scrollHeight: itemTop,
|
||||||
|
start: startIndex,
|
||||||
// Check item bottom in the range. We will render additional one item for motion usage
|
end: endIndex,
|
||||||
if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) {
|
offset: startOffset,
|
||||||
endIndex = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemTop = currentItemBottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to normal if not match. This code should never reach
|
|
||||||
/* istanbul ignore next */
|
|
||||||
if (startIndex === undefined) {
|
|
||||||
startIndex = 0;
|
|
||||||
startOffset = 0;
|
|
||||||
}
|
|
||||||
if (endIndex === undefined) {
|
|
||||||
endIndex = dataLen - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give cache to improve scroll experience
|
|
||||||
endIndex = Math.min(endIndex + 1, dataLen);
|
|
||||||
calRes.value = {
|
|
||||||
scrollHeight: itemTop,
|
|
||||||
start: startIndex,
|
|
||||||
end: endIndex,
|
|
||||||
offset: startOffset,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ immediate: true, flush: 'post' },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// =============================== In Range ===============================
|
// =============================== In Range ===============================
|
||||||
const maxScrollHeight = computed(() => calRes.value.scrollHeight! - props.height!);
|
const maxScrollHeight = computed(() => calRes.scrollHeight! - props.height!);
|
||||||
|
|
||||||
function keepInRange(newScrollTop: number) {
|
function keepInRange(newScrollTop: number) {
|
||||||
let newTop = newScrollTop;
|
let newTop = newScrollTop;
|
||||||
|
@ -416,15 +455,6 @@ const List = defineComponent({
|
||||||
setInstance,
|
setInstance,
|
||||||
mergedData,
|
mergedData,
|
||||||
} = this;
|
} = this;
|
||||||
const listChildren = renderChildren(
|
|
||||||
mergedData,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
setInstance,
|
|
||||||
children,
|
|
||||||
sharedConfig,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -446,9 +476,11 @@ const List = defineComponent({
|
||||||
offset={offset}
|
offset={offset}
|
||||||
onInnerResize={collectHeight}
|
onInnerResize={collectHeight}
|
||||||
ref="fillerInnerRef"
|
ref="fillerInnerRef"
|
||||||
>
|
v-slots={{
|
||||||
{listChildren}
|
default: () =>
|
||||||
</Filler>
|
renderChildren(mergedData, start, end, setInstance, children, sharedConfig),
|
||||||
|
}}
|
||||||
|
></Filler>
|
||||||
</Component>
|
</Component>
|
||||||
|
|
||||||
{useVirtual && (
|
{useVirtual && (
|
||||||
|
|
Loading…
Reference in New Issue