๐จ TroubleShotting
๋ฌธ์ ๋ฐ์์ ๋ฐฐ๊ฒฝ - As Is
๋ฌดํ์คํฌ๋กค ์ฌ์ฉ ์ด์
1. ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๋ถ๋ฌ์ฌ ๋ ๋งค๋๋ฌ์ด UI
2. ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ refetch ํ์ง ์๊ณ ์๋ก์ด ๋ฐ์ดํฐ๋ง fetchํด์ ๋ถํ์ํ ๋คํธ์ํฌ ์์ฒญ๋์ ์ค์ผ ์ ์๊ธฐ ๋๋ฌธ์
์ด๋ฌํ ์ด์ ๋ก useInfiniteQuery๋ฅผ ์ด์ฉํด์ ๋ฌดํ์คํฌ๋กค์ ๊ตฌํํ๊ธฐ๋ก ํ๋ค.
๊ทธ๋ฐ๋ฐ, intersection-observer ์์ด ๋ฌดํ์คํฌ๋กค์ ๊ตฌํํด๋ณด๊ณ ์ถ์๋ค.
๊ตณ์ด? ์ถ๊ธฐ๋ ํ์ง๋ง ์ฝ๋ฉ์ ์คํฉ์ ์ํ์ฐฉ์ค์ ๊ฒฐ๊ณผ๋ฌผ์ด๋ผ๊ณ ํ์ผ๋..! ํจ ํด๋ณด์
๋ฐฉ๋ฒ์ ๋ ๊ฐ์ง๊ฐ ์์ ๊ฒ ๊ฐ๋ค.
1. ์คํฌ๋กค ๋ด๋ฆฐ ๋์ด๊ฐ ์ ์ฒด ๋์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ์ฒด ๋์ด์ ์ด๋์ ๋๋ฅผ ๋๊ธฐ๋ฉด fetchNextPage()
2. ์คํฌ๋กค ๋ด๋ฆฐ ๋์ด๊ฐ intersection-observer์ useInview์ฒ๋ผ ํน์ ref ์์๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ช ํผ์ผํธ๋ฅผ ๋๊ธฐ๋ฉด fetchNextPage()
How(๊ณผ์ ) ?
์ํ์ฐฉ์ค 1
์ฒ์์๋ ๋ด๊ฐ ์คํฌ๋กคํ ๋์ด vs ์ ์ฒด document์ ๋์ด์ ๋น๊ตํด์
์๋ฅผ ๋ค๋ฉด, ์คํฌ๋กคํ ๋์ด๊ฐ ์ ์ฒด document์ x 0.8 ๋ณด๋ค ํฌ๋ฉด fetchNextPage()๋ฅผ ํ๋๋ก ํ๋ คํ๋ค.
๊ทธ๋์ ์ฐ์ ๋ด๊ฐ ์คํฌ๋กคํ ๋์ด์ ์ ์ฒด document์ ๋์ด๋ฅผ ๊ตฌํด๋ณด์๋ค.
const QuizCommentsList = () => {
const {
data: quizComments,
isFetchingNextPage,
isFetchingPreviousPage,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isRefetching
} = useQuizCommentsQuery();
const [scrollY, setScrollY] = useState(0);
const [documentHeight, setDocumentHeight] = useState(0);
const docHeight = document.documentElement.scrollHeight;
const handleScroll = () => {
setScrollY(window.scrollY);
};
const updateDocumentHeight = () => {
setDocumentHeight(document.documentElement.scrollHeight);
};
useEffect(() => {
// ์คํฌ๋กค ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
window.addEventListener('scroll', handleScroll);
updateDocumentHeight();
window.addEventListener('resize', updateDocumentHeight);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', updateDocumentHeight);
};
}, []);
console.log('scrollY =>', scrollY);
console.log('docHeight =>', docHeight);
console.log('documentHeight =>', documentHeight);
return (
์ฌ๊ธฐ์ ๊ถ๊ธ์ฆ.
document์ ์ ์ฒด ๋์ด? ๋ฅผ ๋ฐ๋ก
const docHeight = document.documentElement.scrollHeight;
์ปดํฌ๋ํธ ์๋จ์ ์ด๋ ๊ฒ ์ ์ธํ๋ ๊ฒ๊ณผ
const [documentHeight, setDocumentHeight] = useState(0);
...
const updateDocumentHeight = () => {
setDocumentHeight(document.documentElement.scrollHeight);
};
useEffect(() => {
...
updateDocumentHeight();
window.addEventListener('resize', updateDocumentHeight);
return () => {
window.removeEventListener('resize', updateDocumentHeight);
};
}, []);
์ํ๋ฅผ ๋ง๋ ๋ค useEffect ๋ด์์ set ํด์ค ๊ฒฐ๊ณผ๊ฐ์ด ๋ฌ๋๋ค.
์ ๋ค๋ฅด์ง? ๋ญ๊ฐ ๋ ๋ง๋ ๊ฑธ๊น?
ํ์ ์ฆ, useEffect ๋ด์์ document์ ์ ์ฒด ๋์ด๋ฅผ ๊ตฌํ๋ ๋ฐ์ ๋ ๋ง๋ ๋ฐฉ๋ฒ์ด๋ค.
document์ ๋ง์ดํธ ๋์ด ๋ ๋๋ง์ด ์๋ฃ๋๋ ๊ณผ์ ์์ DOM ์ํ๊ฐ ๋ฌ๋ผ์ง ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
OK. document์ ์ ์ฒด ๋์ด๋ ๊ตฌํ๋๋ฐ ๋ ๊ถ๊ธ์ฆ.
scrollY !== documentHeight?
์ด๋๋ก ์คํฌ๋กค์ ๊ฐ์ฅ ๋ฐ์ผ๋ก ๋ด๋ฆฌ๋ฉด, scrollY์ documentHeight์ ๊ฐ์ด ๊ฐ์์ผ ํ๋๋ฐ ๋ฌ๋๋ค.
์๊ณ ๋ณด๋ scrollY๋ ์ง๊ธ๊น์ง ์คํฌ๋กค ํ ๊ณณ๊น์ง์ ๋์ด๋ ๋ง์ง๋ง,
์คํฌ๋กคํ ๊ณณ์ ๊ฐ์ฅ ์๋จ๊น์ง์ ๋์ด๋ผ๊ณ ํ๋ค.
์ข ๋ ์ ํํ๊ฒ๋ mdn ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด scroll Y๋ ํ์ฌ ๋ทฐํฌํธ ์์ชฝ ๋ชจ์๋ฆฌ์ Y์ขํ๋ฅผ ๋ฐํ ํ๋ค๊ณ ๋์ด์๋ค.
https://developer.mozilla.org/ko/docs/Web/API/Window/scrollY
๋ฐ๋ผ์, scrollY์ documentHeight๋ฅผ ๊ฐ๊ฒ ํ๋ ค๋ฉด scrollY + ๋ทฐํฌํธ์ ๋์ด๋ฅผ ๋ํด์ค์ผ ํ๋ค.
์ํ์ฐฉ์ค 2
๊ทธ๋์ documentHeight๋ฅผ ์ ๋๋ก ๊ตฌํ๊ธฐ๋ก ํ๊ณ , scrollY + viewportHeihgt === documentHeight๊ฐ ๋๋๋ก ์์ ํ๋ค.
const handleScroll = () => {
const scrollPosition = window?.scrollY;
setScrollY(scrollPosition);
};
const updateDocumentHeight = () => {
setDocumentHeight(document.documentElement.scrollHeight);
setViewportHeight(window?.innerHeight);
};
const [scrollY, setScrollY] = useState(0);
const [viewportHeight, setViewportHeight] = useState(0);
const [documentHeight, setDocumentHeight] = useState(0);
useEffect(() => {
handleScroll();
// ์คํฌ๋กค ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
window.addEventListener('scroll', handleScroll);
updateDocumentHeight();
window.addEventListener('resize', updateDocumentHeight);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', updateDocumentHeight);
};
}, []);
console.log('scrollY =>', scrollY);
console.log('viewportHeight =>', viewportHeight);
console.log('scrollY + viewportHeight =>', scrollY + viewportHeight);
console.log('documentHeight =>', documentHeight);
console.log('------------------------------------');
์๋ ๊ทธ๋ฐ๋ฐ๋ ์คํฌ๋กค์ ์ต์๋จ์ผ๋ก ๋ด๋ ธ์ ๋, documentHeight์ scrollY + viewportHeight๊ฐ ๋ค๋ฅธ ๊ฒ ์๋๊ฐใ
2100 !== 1629....
๋ญ๊ฐ ๋ฌธ์ ์ธ์ง ํ์ฐธ์ ์ณ๋ค๋ณด๋ ๋น์ฐํ ๊ฒฐ๊ณผ์๋ค.
๋๋ ์ง๊ธ viewportHeight์ documentHeight ๊ฐ์ ๋ง์ดํธ ๋ ํ์ resize ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋๋ง ์
๋ฐ์ดํธ ํ๊ณ ์์๋ค..
scroll ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ scrollY์ ํ๊บผ๋ฒ์ ์
๋ฐ์ดํธ ํด์ผํ๋๋ฐ ๋ง์ด๋ค. U hoxy ๋ฐ์นด??
๋ค์ ๋ฐ๊ฟจ๋ค.
const {
data: quizComments,
isFetchingNextPage,
isFetchingPreviousPage,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isRefetching
} = useQuizCommentsQuery();
const [scrollY, setScrollY] = useState(0);
const [viewportHeight, setViewportHeight] = useState(0);
const [documentHeight, setDocumentHeight] = useState(0);
const handleScroll = () => {
// ํ๊บผ๋ฒ์ ์
๋ฐ์ดํธ
const scrollPosition = window?.scrollY;
setScrollY(scrollPosition);
setDocumentHeight(document.documentElement.scrollHeight);
setViewportHeight(window?.innerHeight);
};
useEffect(() => {
handleScroll();
// ์คํฌ๋กค ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleScroll);
};
}, []);
console.log('scrollY =>', scrollY);
console.log('viewportHeight =>', viewportHeight);
console.log('scrollY + viewportHeight =>', scrollY + viewportHeight);
console.log('documentHeight =>', documentHeight);
console.log('------------------------------------');
๋คํํ ์ด๋ ๊ฒ ํ๋๋ ์คํฌ๋กค์ ๊ฐ์ฅ ํ๋จ์ผ๋ก ๋ด๋ ธ์ ๋
scrollY + viewportHeight ๊ฐ documentHeight์ ๊ฑฐ์ ๋น์ทํ ์ซ์๊ฐ ๋์๋ค. ํด..
์ ๊ทธ๋ผ ์ด๋ฐ ๊ฐ๋ ๋ค์ ์์์ผ๋ ๋๋์ด!! ๋ฌดํ์คํฌ๋กค์ ๊ตฌํํด๋ณด์!
const handleScroll = useCallback(() => {
const scrollPosition = window?.scrollY;
const docHeight = document.documentElement.scrollHeight;
const viewHeight = window.innerHeight;
if (scrollPosition + viewHeight >= docHeight * 0.8) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
useEffect(() => {
handleScroll();
// ์คํฌ๋กค ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleScroll);
};
}, [handleScroll]);
์ด๋ ๊ฒ ํ ๋ค์ ๊ตฌํ๋ ๊ฑธ ๋ณด๋ฉด ์ด๋ ๊ฒ ๋๋ค.
์ํ์ฐฉ์ค 3
๊ทธ๋ฐ๋ฐ ์... ์ด๋ ๊ฒ ํ๋๊น
๋ณด๋ค์ํผ Devtools์๋ ์คํฌ๋กค ๋ค์ด์ ๋ฐ๋ผ์ ์๋ก์ด ๋ฐ์ดํฐ๋ค์ ๋์ ํ์ฌ ๋ฐ์์ง๋ ๊ฒ์ ๋์ง๋ง,
์ ์ ๋ก ํ์ฌ๊ธ ์ธ์ ์ ํํ ๋ฐ์ดํฐ๋ฅผ ๋ fetching ํ๋ ๊ฒ์ธ์ง ์ ์๊ฐ ์์๋ค.
๊ทธ๋์ ๋ ๋ฒ์งธ ๋ฌดํ์คํฌ๋กค ๋ฐฉ๋ฒ์ธ
intersection-observer์ useInview๋ฅผ ์ฌ์ฉํ ๋์ ๊ฐ์ด ์ด๋ค ์์์ ref ๊ฐ์ ์ค ๋ค
๊ทธ ์์์ ์ผ์ ํผ์ผํธ ์ด์ ์คํฌ๋กค ๋ค์ด์ด ๋๋ฉด fetchNextPage() ํจ์๋ฅผ ํธ์ถํ๋ ๊ฒ๋ ์๋ํด๋ณด์๋ค.
์ด๋ ๊ฒ ๋๋ฉด ์ด๋์ ๋๊ฐ ๋์ด์ผ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ์ ์ผ๋ก fetch ๋๋์ง ๊ฐ์์ ์ผ๋ก ๋ ๋ช
ํํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฐ์ ๊ธฐ์ค์ด ๋ ์์์ ref๋ฅผ ๋ถ์ฌํ๋ค.
const QuizCommentsList = () => {
const {
data: quizComments,
isFetchingNextPage,
isFetchingPreviousPage,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isRefetching
} = useQuizCommentsQuery();
const targetRef = useRef<HTMLDivElement | null>(null);
...
return (
...
<div ref={targetRef} className="w-[300px] h-[300px] bg-gray-300"></div>
)
};
๊ทธ๋ผ ๊ทธ ref์ ๊ฐ์ฅ ์๋จ๊น์ง์ ๋์ด๋ฅผ ์ด๋ป๊ฒ ๊ตฌํ ๊น?
์์ ์ ์คํฌ๋กค ํ ๋ ์ฌ์ฉํ๋ getBoundingClientRect()๊ฐ ์๊ฐ๋ฌ๋ค.
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์ ๋ ์ฃผ์ํ ์ !
getBoundingClientRect().top ์ผ๋ก ์์์๋ถํฐ ~ ํด๋น ์์์ ์๋จ๊น์ง ์์น๋ฅผ ๊ตฌํ ๋๋ ๊ทธ '์' ๋ผ๋ ๊ธฐ์ค์ด
์ ์ฒด document๊ฐ ์๋๋ผ, ๋ทฐํฌํธ์ ์๋จ์ด ๋๋ค.
https://developer.mozilla.org/ko/docs/Web/API/Element/getBoundingClientRect
๊ฒฐ๊ตญ์๋ getBoundingClientRect().top ๋ง ํด์๋ ์ ๋๊ณ ,
์ง๊ธ๊น์ง scrollํ ๊ณณ๊น์ง์ ๋์ด + getBoundingClientRect().top ์ ํด์ผ
์ค์ ๋ก ํด๋น ์์์ ์๋จ๊น์ง์ ๊ฑฐ๋ฆฌ๊ฐ ๋์จ๋ค.
์ด๊ฑธ ๋ฐํ์ผ๋ก ์ฝ๋๋ฅผ ์ง๋ฉด..!!
what(๊ฒฐ๊ณผ) - To Be
const QuizCommentsList = () => {
const {
data: quizComments,
isFetchingNextPage,
isFetchingPreviousPage,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isRefetching
} = useQuizCommentsQuery();
const targetRef = useRef<HTMLDivElement | null>(null);
const handleScroll = useCallback(() => {
const scrollPosition = window.scrollY;
const viewportHeight = window.innerHeight;
if (targetRef.current) {
const rect = targetRef.current.getBoundingClientRect();
const targetTop = rect.top + scrollPosition;
const targetPosition = targetTop + rect.height * 0.5;
if (targetPosition <= scrollPosition + viewportHeight) {
if (!hasNextPage || isFetchingNextPage) return;
fetchNextPage();
}
}
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
useEffect(() => {
handleScroll();
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleScroll);
};
}, [handleScroll]);
return (
...
๐ก ์๋กญ๊ฒ ์๊ฒ๋ ์
์คํฌ๋กค์ ๋ด๋ฆด ๋์ ์์น๋ scrollY์ ๋ทฐํฌํธ์ ๋์ด๋ฅผ ๋ํด์ค์ผ ํ๋ค๋ ๊ฒ๊ณผ
getBoundingClientRect()๋ ๋ทฐํฌํธ์ ์๋จ์ ๊ธฐ์ค์ผ๋ก ์์์ ์๋จ๊น์ง์ ๊ฑฐ๋ฆฌ๋ผ๋ ๊ฒ์ ๋ฐฐ์ ๋ค!