Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/client/src/shared/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export { useSaveWorkspace } from './queries/useSaveWorkspace';
export { usePostImage } from './queries/usePostImage';
export { useDeleteImage } from './queries/useDeleteImage';

export { useWindowSize } from './css/useWindowSize';
export { useCssTooltip } from './css/useCssTooltip';
export { useCssOptions } from './css/useCssOptions';
export { useCssOptionItem } from './css/useCssOptionItem';
Expand All @@ -16,3 +15,6 @@ export { workspaceKeys } from './query-key/workspaceKeys';

export { usePreventLeaveWorkspacePage } from './usePreventLeaveWorkspacePage';
export { useInfiniteScroll } from './useInfiniteScroll';
export { useScrollPosition } from './useScrollPosition';
export { useWindowSize } from './useWindowSize';
export { useVirtualScroll } from './useVirtualScroll';
25 changes: 25 additions & 0 deletions apps/client/src/shared/hooks/useScrollPosition.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쓰로틀링 좋네요!
커스텀 훅으로 계속 잘 관리해 주시네용 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useRef, useState } from 'react';

export const useScrollPosition = () => {
const [scrollPosition, setScrollPosition] = useState<number>(0);
const ticking = useRef(false);

useEffect(() => {
const onScroll = () => {
if (!ticking.current) {
requestAnimationFrame(() => {
setScrollPosition(window.scrollY);
ticking.current = false;
});
ticking.current = true;
}
};
window.addEventListener('scroll', onScroll);

return () => {
window.removeEventListener('scroll', onScroll);
};
}, []);

return { scrollPosition };
};
56 changes: 56 additions & 0 deletions apps/client/src/shared/hooks/useVirtualScroll.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

계산 로직이 복잡했을텐데 척척 다 잘하셨군요 💯

nodePadding 계산 로직이 따로 쓰이는 부분이 없으니, 파일분리는 안하고 같은 파일에서 함수로만 따로뺀거 좋네용 🥇

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useEffect, useState } from 'react';
import { useScrollPosition, useWindowSize } from '@/shared/hooks';

const calculateNumOfNodePadding = (windowWidth: number): number => {
if (windowWidth < 640) {
return 1;
} else if (windowWidth < 768) {
return 2;
} else if (windowWidth < 1024) {
return 3;
}
return 4;
};

export const useVirtualScroll = <T>({
data,
topSectionHeight = 0,
renderedItemHeight,
gapY = 0,
}: {
data: T[] | undefined;
topSectionHeight?: number;
renderedItemHeight: number;
gapY: number;
}) => {
const { scrollPosition } = useScrollPosition();
const { screenHeight, screenWidth } = useWindowSize();

const [renderedData, setRenderedData] = useState<T[]>([]);

const nodePadding = calculateNumOfNodePadding(screenWidth);
const start = Math.max(
0,
Math.floor((scrollPosition - topSectionHeight) / (renderedItemHeight + gapY)) * nodePadding
);
const offsetY = Math.floor(start / nodePadding) * (renderedItemHeight + gapY);
const [totalHeight, setTotalHeight] = useState(0);

useEffect(() => {
if (!data) {
return;
}

setRenderedData(
data.slice(
start,
start +
Math.floor(screenHeight / (renderedItemHeight + gapY)) * nodePadding +
2 * nodePadding
)
);
setTotalHeight(Math.ceil(data!.length / nodePadding) * (renderedItemHeight + gapY));
}, [start, screenHeight, data]);

return { renderedData, offsetY, totalHeight };
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { EmptyWorkspace, WorkspaceGrid, WorkspaceHeader, WorkspaceList } from '@/widgets';
import { useGetWorkspaceList, useInfiniteScroll } from '@/shared/hooks';
import { useGetWorkspaceList, useInfiniteScroll, useVirtualScroll } from '@/shared/hooks';

import { SkeletonWorkspaceList } from '@/shared/ui';
import { TWorkspace } from '@/shared/types';
import { WorkspaceLoadError } from '@/entities';

/**
Expand All @@ -13,6 +14,13 @@ export const WorkspaceContainer = () => {
const { hasNextPage, fetchNextPage, isPending, isFetchingNextPage, isError, workspaceList } =
useGetWorkspaceList();

const { renderedData, offsetY, totalHeight } = useVirtualScroll<TWorkspace>({
data: workspaceList,
topSectionHeight: 594,
renderedItemHeight: 262,
gapY: 32,
});

const fetchCallback: IntersectionObserverCallback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasNextPage) {
Expand All @@ -39,10 +47,16 @@ export const WorkspaceContainer = () => {
(workspaceList.length === 0 ? (
<EmptyWorkspace />
) : (
<WorkspaceGrid>
<WorkspaceList workspaceList={workspaceList} />
{isFetchingNextPage && <SkeletonWorkspaceList skeletonNum={8} />}
</WorkspaceGrid>
<div
style={{
height: `${totalHeight}px`,
}}
>
<WorkspaceGrid offsetY={offsetY}>
<WorkspaceList workspaceList={renderedData} />
{isFetchingNextPage && <SkeletonWorkspaceList skeletonNum={8} />}
</WorkspaceGrid>
</div>
))
)}
{!isPending && !isFetchingNextPage && hasNextPage && (
Expand Down
15 changes: 12 additions & 3 deletions apps/client/src/widgets/home/WorkspaceGrid/WorkspaceGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { PropsWithChildren } from 'react';
import { ReactNode } from 'react';

/**
*
* @description
* 워크스페이스 그리드 컴포넌트
*/
export const WorkspaceGrid = ({ children }: PropsWithChildren) => {
export const WorkspaceGrid = ({
offsetY = 0,
children,
}: {
offsetY?: number;
children: ReactNode;
}) => {
return (
<div>
<div
style={{ transform: offsetY > 0 ? `translateY(${offsetY + 32}px)` : 'none' }}
will-change="transform"
>
<div className="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{children}
</div>
Expand Down
Loading