fix: tree render perf, close #5069

pull/5079/head
tangjinzhou 2021-12-24 14:15:10 +08:00
parent 4e70c6dd77
commit b6c1b3ed86
1 changed files with 109 additions and 77 deletions

View File

@ -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 && (