ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด์๋ค. ํ๋ก ํธ์์๋ ์ฒ์ ๊ตฌํํ๋ ๊ธฐ๋ฅ์ด์์ง๋ง ๋ฐฑ์๋๋ ์ฒ์์ด๋ผ ์ฌ๋ฌ ์ํ์ฐฉ์ค๋ฅผ ๊ฒช์๋ค. ๊ทธ๋์ ๊ทธ ๊ณผ์ ์ ๊ฐ๋ตํ ๊ธฐ๋กํ๊ณ ์ ํ๋ค.
#. ๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ
๋ง์ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ ๋ถ๋ฌ์ค๊ฒ ๋๋ฉด ๊ทธ๋งํผ ์ฌ์ฉ์์๊ฒ ๋ก๋ฉ๋์ด ๋ณด์ฌ์ง๋ ์๋๊ฐ ๋ฆ์ด์ง๊ฒ ๋๋ค. ๋ฐ๋ผ์ ์ฒ์ ๋ก๋ฉ๋์์ ๋ ์ฌ์ฉ์์๊ฒ ์ผ์ ๋ฐ์ดํฐ๋ง ๋จผ์ ์ ๊ณตํ๊ณ ์ดํ ์ฌ์ฉ์๊ฐ ๋ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ ค๊ณ ์คํฌ๋กค์ ์์ง์ด๋ ์ก์ ์ ์ทจํ๋ฉด, ๊ทธ๋ ์ถ๊ฐ๋ก ๋ ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํด์ฃผ๋ ๊ฒ์ ๋ฐ๋ณตํ๋ ๊ฒ์ด ๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ์ด๋ค.
#. ๋ฐฑ์๋ ๊ตฌํ
๋ชฉํ: ํด๋ผ์ด์ธํธ์์ ๋ฐ์ดํฐ ์์ฒญ ์ ์๋ง๋ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๋ ๊ฒ!
๋ฐฑ์๋๋ฅผ ๊ตฌํํ๋๋ฐ ๊ฐ์ฅ ์ํ์ฐฉ์ค๋ฅผ ๊ฒช์๋ค. ๋ฐฑ์๋์์ ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ฃผ๋๋์ ๋ฐ๋ผ ํ๋ก ํธ์๋์์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ฐฉ์๋ ๋ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์ ๋ฉ๋ฌ์ ์ฌ๋ฌ๋ฒ ์์ ์ด ์ด๋ฃจ์ด์ก๋ค.
์ฒ์์ ๋ฐฑ์๋๋ฅผ ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
์ผ๋ก ๋์ํ๊ฒ๋ ์ฝ๋๋ฅผ ์งฐ๋ค. ํ๋ก ํธ์๋์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ด ๋ค๋ฅผ ๋ฟ ์๋ฒ์์ ๋ฌดํ ์คํฌ๋กค์ด๋ ํ์ด์ง๋ค์ด์
์ด๋ ๋๊ฐ์ด ์ถ๊ฐ๋๋ ๋ฐ์ดํฐ๋ฅผ ์ฐพ์ ์ ๋ฌํด์ฃผ๋ฉด ๋๋ค๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋์ ํ๋ก ํธ์๋์์ ์ฒซ ๋๋๋ง ๋์ด์ง๋ ๋ฐ์ดํฐ์ ๋ง์ง๋ง ๋ฒํธ๋ฅผ ๊ธฐ์ค์ผ๋ก cursorId๋ฅผ ์ ๋ฌํ์ฌ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์๋ค.
๊ทธ๋ฐ๋ฐ ์น๊ตฌ์ ํผ๋๋ฐฑ์ผ๋ก ์ด๋ฐ ๋ฐฉ์๋ณด๋จ ์๋ฒ์์ ๋ฐ์ดํฐ ์๋ต ์ ๋ค์ ํ์ด์ง๋ฅผ ์์ฒญํ ์ ์๋ api ์ฃผ์๋ฅผ ๊ฐ์ด ์ ๋ฌํ๋๊ฒ ๋ ์ข์๊ฑฐ ๊ฐ๋ค๋ ๋ง์ ๋ค์๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ง์ ๋ฃ๊ณ ๋ณด๋ ์์ผ YouTube API๋ ์ด๋ฌํ ๊ตฌ์กฐ์์ ๋ ์ฌ๋ ธ๋ค.
YouTube API๋ ํ๋ฒ์ ์ต๋ 50๊ฐ์ฉ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ ๋ฐ์ ์ ์๊ณ ๊ทธ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ผ๋ ค๋ฉด ์๋ต ์ ๋ฐ์ nextPageToken์ ํจ๊ป ๋ณด๋ด์ผ ํ๋ค. ๊ทธ๋์ ์ ํ๋ธ๋ ๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ์ด ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์ ์น๊ตฌ์ ํผ๋๋ฐฑ์ด ๋ ์ ์ ํ ๋ฐฉ๋ฒ์ด๊ตฌ๋ ์ถ์ด ์ ๋ฉด ์์ ํ๊ฒ ๋์๋ค. ์์ ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
// controller code
export const latestVideosByPage = async (req, res) => {
const { pageNum } = req.query;
const data = await getLatestVideosByPage(pageNum);
let nextPage =
data.length === 0 ? "" : `videos/latest?pageNum=${parseInt(pageNum) + 1}`;
return res.json({ data, nextPage });
};
// model code
export const getLatestVideosByPage = async (pageNum) => {
const SELECT_VIDEO = `select etag, id, snippet from latestVideos where idx > ${
(pageNum - 1) * 15
} limit 15;`;
const [row, field] = await connection.promise().query(SELECT_VIDEO);
return row;
};
์ ์ฝ๋๋ฅผ ๊ฐ๋ตํ ์ค๋ช
ํ์๋ฉด, ์ด๊ธฐ ๋ฐ์ดํฐ ์๋ต ์ nextPage ๊ฐ์ผ๋ก 'http://localhost:5000/videos/latest?pageNum=2'๋ฅผ ์ ๋ฌํ๋ค. ๊ทธ๋ผ ํ๋ก ํธ์๋์์ ๋ค์ ๋ฐ์ดํฐ ์์ฒญ ์ ํด๋น url๋ก ์๋ฒ์ ์์ฒญํ๊ณ , ์๋ฒ์์ query๋ก pageNum ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ค์ ํ์ด์ง์ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ DB์์ ์ฐพ์์จ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์ ๋ฌํ ๋ ๋ ๋ค์ ํ์ด์ง ์์ฒญ url์ธ 'http://localhost:5000/videos/latest?pageNum=3'์ ํจ๊ป ์ ๋ฌํ๋ ๊ตฌ์กฐ์ด๋ค. ๋! (** ์ฐธ๊ณ ๋ก ๋๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ฅผ ์ค๋น์ค์ด๋ผ ๋ฐฑ์๋ ๊ฐ๋ฐ ์์ค์ด ๋ฏธํกํ ์ ์๋ค ๐)
#. ํ๋ก ํธ์๋ ๊ตฌํ
๋ชฉํ: ์ฌ์ฉ์๊ฐ ํ๋ฉด์ ์คํฌ๋กคํด์ ํน์ ์์น๊ฐ ๋ฟ์ผ๋ฉด ์๋ฒ์๊ฒ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ๋ก ์์ฒญํ์ฌ ์ฌ์ฉ์์๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ!
๋ฌดํ ์คํฌ๋กค ๊ธฐ๋ฅ์ ๋ํด ๊ตฌ๊ธ๋ง ํด๋ณด๋ ํฌ๊ฒ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์๋ค. ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ window์ ์คํฌ๋กค ์ด๋ฒคํธ๋ฅผ ํตํด ํ๋ฉด ์คํฌ๋กค์ด ๋งจํ๋จ์ ๋ฟ์์ ๋๋ฅผ ๊ฐ์งํ๋ ๊ฒ. ๋๋ฒ์งธ ๋ฐฉ๋ฒ์ react-intersection-observer๋ฅผ ์ด์ฉํด ๋งจํ๋จ์ ํน์ ํ๊ทธ๊ฐ ๋ณด์ฌ์ง ๋๋ฅผ ๊ฐ์งํ๋ ๊ฒ.
์ฒ์์ ๋๋ฒ์งธ ๋ฐฉ๋ฒ์ธ react-intersection-observer์ ์ด์ฉํด ๊ตฌํํ๋ค. react-intersection-observer๋ฅผ ์ค์นํ๊ณ ํ์ด์ง ๋งจ ํ๋จ์ <span> ํ๊ทธ๋ฅผ ๋ง๋ค์ด ๊ฑฐ๊ธฐ์ ref ์์ฑ์ ์ง์ ํด๋์๋ค. ๊ทธ๋ผ ํ๋ฉด์ ์คํฌ๋กคํด์ ํด๋น ํ๊ทธ๊ฐ ๋ณด์ฌ์ง ๋๋ง๋ค inview ๊ฐ์ด false์์ true๋ก ๋ณ๊ฒฝ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ฐ์ด true๊ฐ ๋ ๋ ์๋ฒ์ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ api๋ฅผ ํธ์ถํ๋ค. (*ref, inview๋ react-intersection-observer์์ ์ ๊ณตํ๋ ๊ฒ)
๊ทธ๋ฐ๋ฐ ์ด์์ด๋ฉด ๋ค๋ฅธ ํจํค์ง ์ฌ์ฉ ์์ด ์ง์ ๊ตฌํํด๋ณด๋ ๊ฒ๋ ์ข์๊ฑฐ ๊ฐ์ ๋ค์ ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ผ๋ก ์์ ํ์๋ค. ๊ทธ๋์ useEffect๋ฅผ ์ด์ฉํด window์ ์คํฌ๋กค ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ์๊ณ , ์คํฌ๋กค์ด ์ํ๋ ์์น๊ฐ ๋์์ ๋ isHitBottom์ด๋ ๊ฐ์ false์์ true๋ก ๋ณ๊ฒฝ๋๋๋ก ํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด๋ ์ด๋ฒคํธ๋ฅผ ์คํํ ๋ค์ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ก ์ ๊ฑฐํ๋ ๊ฒ์ด ์ค์ํ๋ค. ๋ง์ฝ ์ ๊ฑฐํ์ง ์์ผ๋จ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋๋๋ง๋ ๋๋ง๋ค window์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๊ณ์ ์ถ๊ฐํ๊ฒ ๋์ด ์๋์น ์์ ๋์์ด ๋ฐ์ํ๊ฑฐ๋ ๋ฉ๋ชจ๋ฆฌ์ ์ผ๋ก ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ๋ ํ๋ค(๊ทธ๋ ๋ค๊ณ ํ๋ค๐)
๋ฐ๋ผ์ isHitBottom ๊ฐ์ด true๊ฐ ๋ ๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ api๋ฅผ ํธ์ถํ๋ฉด๋๋ค. nextPageNum์ ์๋ฒ์์ ์ด์ ๋ฐ์ดํฐ ์๋ต ์ ๋ณด๋ด์ค ๋ค์ ํ์ด์ง์ api url์ ๋ํ ๊ฐ์ด๋ค. (*๋น๋๊ธฐ ํธ์ถ์ redux-saga๋ฅผ ์ด์ฉํจ)
// component code
const [isHitBottom, setIsHitBottom] = useState(false);
useEffect(() => {
const handleScroll = () => {
const { scroolTop, offsetHeight } = document.documentElement;
if(window.innerHeight + scrollTop >= offsetHeight) {
setIsHitBottom(true);
}
};
setIsHitBottom(false);
window.addEventListener('scroll', hadleScroll);
return () => window.removeEventListener('scroll', handleScroll);
})
useEffect(() => {
if (isHitBottom && nextPageNum.length !== 0) {
dispatch(latestVideosRequest(nextPageNum));
}
}, [isHitBottom]);
// api code
const LATEST_VIDEOS = "/videos/latest";
export const latestVideosByPage = async (pageNum) => {
try {
const res = await axios.get(`${LATEST_VIDEOS}?pageNum=${pageNum}`);
if (res) return res.data;
} catch (err) {
console.log(err);
}
};
'๊ฐ๋ฐ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
session vs jwt ๋ก๊ทธ์ธ (0) | 2022.10.07 |
---|---|
redux-saga๋ฅผ ์ด์ฉํ ๋น๋๊ธฐ ๊ตฌํ (0) | 2022.10.06 |
์๋ฐ์คํฌ๋ฆฝํธ ๋๊ธฐ์ ๋น๋๊ธฐ (0) | 2022.09.29 |
React + Redux(Redux-toolkit) + Redux-saga (3) | 2022.09.28 |
redux-saga (2) | 2022.09.27 |