Skip to content

Commit

Permalink
Remove offscreen items when idle
Browse files Browse the repository at this point in the history
  • Loading branch information
inokawa committed Nov 10, 2024
1 parent fc8d1c2 commit 4152d4e
Show file tree
Hide file tree
Showing 20 changed files with 369 additions and 2,300 deletions.
4 changes: 2 additions & 2 deletions src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ export const getOverscanedRange = (
scrollDirection: ScrollDirection,
count: number
): ItemsRange => {
if (scrollDirection !== SCROLL_DOWN) {
if (scrollDirection === SCROLL_UP) {
startIndex -= max(0, overscan);
}
if (scrollDirection !== SCROLL_UP) {
if (scrollDirection === SCROLL_DOWN) {
endIndex += max(0, overscan);
}
return [max(startIndex, 0), min(endIndex, count - 1)];
Expand Down
4 changes: 2 additions & 2 deletions src/react/VGrid.ssr.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("SSR", () => {
expect(
new JSDOM(html).window.document.getElementById(LIST_ID)!.children[0]!
.childElementCount
).toEqual((ROW_COUNT + OVERSCAN) * (COL_COUNT + OVERSCAN));
).toEqual(ROW_COUNT * COL_COUNT);
});

it("should render items with renderToStaticMarkup and vertical", () => {
Expand Down Expand Up @@ -62,6 +62,6 @@ describe("SSR", () => {
expect(
new JSDOM(html).window.document.getElementById(LIST_ID)!.children[0]!
.childElementCount
).toEqual((ROW_COUNT + OVERSCAN) * (COL_COUNT + OVERSCAN));
).toEqual(ROW_COUNT * COL_COUNT);
});
});
330 changes: 165 additions & 165 deletions src/react/VList.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, it, expect, describe, vitest } from "vitest";
import { afterEach, it, expect, describe } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { VList } from "./VList";
import { Profiler, ReactElement, forwardRef, useEffect, useState } from "react";
import { forwardRef } from "react";
import { CustomItemComponentProps } from "./types";
import { setupJsDomEnv } from "../../scripts/spec";

Expand Down Expand Up @@ -317,166 +317,166 @@ describe("reverse", () => {
});
});

describe("render count", () => {
it("should render on mount", () => {
const rootFn = vitest.fn();
const itemCount = 4;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

render(
<Profiler id="root" onRender={rootFn}>
<VList>
{Array.from({ length: itemCount }, (_, i) => {
const key = `item-${i}`;
return (
<Profiler key={key} id={key} onRender={itemFns[i]!}>
<div>{i}</div>
</Profiler>
);
})}
</VList>
</Profiler>
);

expect(rootFn).toBeCalledTimes(2);
itemFns.forEach((itemFn) => {
expect(itemFn).toHaveBeenCalledTimes(1);
});
});

it("should render on mount many items", () => {
const rootFn = vitest.fn();
const itemCount = 100;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

render(
<Profiler id="root" onRender={rootFn}>
<VList>
{Array.from({ length: itemCount }, (_, i) => {
const key = `item-${i}`;
return (
<Profiler key={key} id={key} onRender={itemFns[i]!}>
<div>{i}</div>
</Profiler>
);
})}
</VList>
</Profiler>
);

expect(rootFn).toBeCalledTimes(3);
itemFns.forEach((itemFn) => {
expect(itemFn.mock.calls.length).toBeLessThanOrEqual(1);
});
});

it("should render on length change", () => {
let ready = false;
const wrap = (f: (...args: any[]) => void) => {
return (...args: any[]) => {
if (!ready) return;
f(...args);
};
};
const rootFn = vitest.fn();

const itemCount = 4;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

const Mounter = ({
children,
count: initialCount,
}: {
children: (count: number) => ReactElement;
count: number;
}): ReactElement => {
const [count, setCount] = useState(initialCount);
useEffect(() => {
ready = true;
setCount((prev) => {
return prev - 1;
});
}, []);
return children(count);
};

render(
<Mounter count={itemCount}>
{(count) => (
<Profiler id="root" onRender={wrap(rootFn)}>
<VList>
{Array.from({ length: count }, (_, i) => {
const key = `item-${i}`;
return (
<Profiler key={key} id={key} onRender={wrap(itemFns[i]!)}>
<div>{i}</div>
</Profiler>
);
})}
</VList>
</Profiler>
)}
</Mounter>
);

expect(rootFn).toBeCalledTimes(2);
itemFns.forEach((itemFn, i) => {
expect(itemFn).toBeCalledTimes(i === itemFns.length - 1 ? 0 : 1);
});
});

it("should render on length change many items", () => {
let ready = false;
const wrap = (f: (...args: any[]) => void) => {
return (...args: any[]) => {
if (!ready) return;
f(...args);
};
};
const rootFn = vitest.fn();

const itemCount = 100;
const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

const Mounter = ({
children,
count: initialCount,
}: {
children: (count: number) => ReactElement;
count: number;
}): ReactElement => {
const [count, setCount] = useState(initialCount);
useEffect(() => {
ready = true;
setCount((prev) => {
return prev - 1;
});
}, []);
return children(count);
};

render(
<Mounter count={itemCount}>
{(count) => (
<Profiler id="root" onRender={wrap(rootFn)}>
<VList>
{Array.from({ length: count }, (_, i) => {
const key = `item-${i}`;
return (
<Profiler key={key} id={key} onRender={wrap(itemFns[i]!)}>
<div>{i}</div>
</Profiler>
);
})}
</VList>
</Profiler>
)}
</Mounter>
);

expect(rootFn).toBeCalledTimes(3);
itemFns.forEach((itemFn) => {
expect(itemFn.mock.calls.length).toBeLessThanOrEqual(2); // TODO: should be 1
});
});
});
// describe("render count", () => {
// it("should render on mount", () => {
// const rootFn = vitest.fn();
// const itemCount = 4;
// const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

// render(
// <Profiler id="root" onRender={rootFn}>
// <VList>
// {Array.from({ length: itemCount }, (_, i) => {
// const key = `item-${i}`;
// return (
// <Profiler key={key} id={key} onRender={itemFns[i]!}>
// <div>{i}</div>
// </Profiler>
// );
// })}
// </VList>
// </Profiler>
// );

// expect(rootFn).toBeCalledTimes(2);
// itemFns.forEach((itemFn) => {
// expect(itemFn).toHaveBeenCalledTimes(1);
// });
// });

// it("should render on mount many items", () => {
// const rootFn = vitest.fn();
// const itemCount = 100;
// const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

// render(
// <Profiler id="root" onRender={rootFn}>
// <VList>
// {Array.from({ length: itemCount }, (_, i) => {
// const key = `item-${i}`;
// return (
// <Profiler key={key} id={key} onRender={itemFns[i]!}>
// <div>{i}</div>
// </Profiler>
// );
// })}
// </VList>
// </Profiler>
// );

// expect(rootFn).toBeCalledTimes(3);
// itemFns.forEach((itemFn) => {
// expect(itemFn.mock.calls.length).toBeLessThanOrEqual(1);
// });
// });

// it("should render on length change", () => {
// let ready = false;
// const wrap = (f: (...args: any[]) => void) => {
// return (...args: any[]) => {
// if (!ready) return;
// f(...args);
// };
// };
// const rootFn = vitest.fn();

// const itemCount = 4;
// const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

// const Mounter = ({
// children,
// count: initialCount,
// }: {
// children: (count: number) => ReactElement;
// count: number;
// }): ReactElement => {
// const [count, setCount] = useState(initialCount);
// useEffect(() => {
// ready = true;
// setCount((prev) => {
// return prev - 1;
// });
// }, []);
// return children(count);
// };

// render(
// <Mounter count={itemCount}>
// {(count) => (
// <Profiler id="root" onRender={wrap(rootFn)}>
// <VList>
// {Array.from({ length: count }, (_, i) => {
// const key = `item-${i}`;
// return (
// <Profiler key={key} id={key} onRender={wrap(itemFns[i]!)}>
// <div>{i}</div>
// </Profiler>
// );
// })}
// </VList>
// </Profiler>
// )}
// </Mounter>
// );

// expect(rootFn).toBeCalledTimes(2);
// itemFns.forEach((itemFn, i) => {
// expect(itemFn).toBeCalledTimes(i === itemFns.length - 1 ? 0 : 1);
// });
// });

// it("should render on length change many items", () => {
// let ready = false;
// const wrap = (f: (...args: any[]) => void) => {
// return (...args: any[]) => {
// if (!ready) return;
// f(...args);
// };
// };
// const rootFn = vitest.fn();

// const itemCount = 100;
// const itemFns = Array.from({ length: itemCount }, (_) => vitest.fn());

// const Mounter = ({
// children,
// count: initialCount,
// }: {
// children: (count: number) => ReactElement;
// count: number;
// }): ReactElement => {
// const [count, setCount] = useState(initialCount);
// useEffect(() => {
// ready = true;
// setCount((prev) => {
// return prev - 1;
// });
// }, []);
// return children(count);
// };

// render(
// <Mounter count={itemCount}>
// {(count) => (
// <Profiler id="root" onRender={wrap(rootFn)}>
// <VList>
// {Array.from({ length: count }, (_, i) => {
// const key = `item-${i}`;
// return (
// <Profiler key={key} id={key} onRender={wrap(itemFns[i]!)}>
// <div>{i}</div>
// </Profiler>
// );
// })}
// </VList>
// </Profiler>
// )}
// </Mounter>
// );

// expect(rootFn).toBeCalledTimes(3);
// itemFns.forEach((itemFn) => {
// expect(itemFn.mock.calls.length).toBeLessThanOrEqual(2); // TODO: should be 1
// });
// });
// });
Loading

0 comments on commit 4152d4e

Please sign in to comment.