React ์ปค์คํ ํ ์ Effect ์ ๋ฆฌ(cleanup) ๋น๋ฒ์ ์์๋ณด์ธ์. ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ณ , ๋ฆฌ์์ค๋ฅผ ๊ด๋ฆฌํ๋ฉฐ, ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ์ํ ๊ณ ์ฑ๋ฅ์ ์์ ์ ์ธ React ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ๋ฒ์ ๋ฐฐ์๋๋ค.
React ์ปค์คํ ํ Effect ์ ๋ฆฌ(Cleanup): ๊ฒฌ๊ณ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ ๋ง์คํฐํ๊ธฐ
๊ดํํ๊ฒ ์ํธ ์ฐ๊ฒฐ๋ ํ๋ ์น ๊ฐ๋ฐ์ ์ธ๊ณ์์ React๋ ๊ฐ๋ฐ์๋ค์ด ๋์ ์ด๊ณ ์ํธ์์ฉ์ ์ธ ์ฌ์ฉ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌ์ถํ ์ ์๋๋ก ์ง์ํ๋ ์ง๋ฐฐ์ ์ธ ๊ธฐ์ ๋ก ๋ถ์ํ์ต๋๋ค. React ํจ์ํ ์ปดํฌ๋ํธ ํจ๋ฌ๋ค์์ ์ค์ฌ์๋ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํ ๊ฐ๋ ฅํ ๋๊ตฌ์ธ useEffect ํ
์ด ์์ต๋๋ค. ํ์ง๋ง ํฐ ํ์๋ ํฐ ์ฑ
์์ด ๋ฐ๋ฅด๋ฏ, ์ด๋ฌํ ์ดํํธ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ฆฌ(clean up)ํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ ๋จ์ํ ๋ชจ๋ฒ ์ฌ๋ก๊ฐ ์๋๋ผ, ์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ํ๋ ์์ ์ ์ด๊ณ ์ฑ๋ฅ์ด ๋ฐ์ด๋๋ฉฐ ์ ๋ขฐํ ์ ์๋ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ์ถํ๊ธฐ ์ํ ๊ทผ๋ณธ์ ์ธ ์๊ตฌ์ฌํญ์
๋๋ค.
์ด ํฌ๊ด์ ์ธ ๊ฐ์ด๋์์๋ React ์ปค์คํ ํ ๋ด์์ ์ดํํธ ์ ๋ฆฌ์ ์ค์ํ ์ธก๋ฉด์ ๊น์ด ํ๊ณ ๋ค ๊ฒ์ ๋๋ค. ์ ์ ๋ฆฌ๊ฐ ํ์์ ์ธ์ง ํ๊ตฌํ๊ณ , ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ์ ์ธ์ฌํ ์ฃผ์๊ฐ ํ์ํ ์ผ๋ฐ์ ์ธ ์๋๋ฆฌ์ค๋ฅผ ๊ฒํ ํ๋ฉฐ, ์ด ํ์ ๊ธฐ์ ์ ๋ง์คํฐํ๋ ๋ฐ ๋์์ด ๋ ์ค์ฉ์ ์ด๊ณ ์ ์ธ๊ณ์ ์ผ๋ก ์ ์ฉ ๊ฐ๋ฅํ ์์ ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์์ ํ๋ซํผ, ์ ์์๊ฑฐ๋ ์ฌ์ดํธ, ๋๋ ๋ถ์ ๋์๋ณด๋ ์ค ๋ฌด์์ ๊ฐ๋ฐํ๋ , ์ฌ๊ธฐ์ ๋ ผ์๋๋ ์์น๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ์ ๋ฐ์์ฑ์ ์ ์งํ๋ ๋ฐ ๋ณดํธ์ ์ผ๋ก ๋งค์ฐ ์ค์ํฉ๋๋ค.
React์ useEffect ํ ๊ณผ ๊ทธ ์๋ช ์ฃผ๊ธฐ ์ดํดํ๊ธฐ
์ ๋ฆฌ(cleanup)๋ฅผ ๋ง์คํฐํ๋ ์ฌ์ ์ ์์ํ๊ธฐ ์ ์, useEffect ํ
์ ๊ธฐ๋ณธ ์ฌํญ์ ๊ฐ๋ตํ ๋์ง์ด ๋ณด๊ฒ ์ต๋๋ค. React ํ
๊ณผ ํจ๊ป ๋์
๋ useEffect๋ ํจ์ํ ์ปดํฌ๋ํธ๊ฐ ์ฌ์ด๋ ์ดํํธ(side effect)๋ฅผ ์ํํ ์ ์๊ฒ ํด์ค๋๋ค. ์ฌ์ด๋ ์ดํํธ๋ React ์ปดํฌ๋ํธ ํธ๋ฆฌ ์ธ๋ถ๋ก ๋๊ฐ ๋ธ๋ผ์ฐ์ , ๋คํธ์ํฌ ๋๋ ๋ค๋ฅธ ์ธ๋ถ ์์คํ
๊ณผ ์ํธ์์ฉํ๋ ์์
์ ์๋ฏธํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ, DOM ์๋ ๋ณ๊ฒฝ, ๊ตฌ๋
์ค์ ๋๋ ํ์ด๋จธ ์์ ๋ฑ์ด ํฌํจ๋ ์ ์์ต๋๋ค.
useEffect์ ๊ธฐ์ด: ์ดํํธ๋ ์ธ์ ์คํ๋๋๊ฐ
๊ธฐ๋ณธ์ ์ผ๋ก useEffect์ ์ ๋ฌ๋ ํจ์๋ ์ปดํฌ๋ํธ์ ๋ชจ๋ ๋ ๋๋ง์ด ์๋ฃ๋ ํ์ ์คํ๋ฉ๋๋ค. ์ด๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ๊ด๋ฆฌํ์ง ์์ผ๋ฉด ์ฌ์ด๋ ์ดํํธ๊ฐ ๋ถํ์ํ๊ฒ ์คํ๋์ด ์ฑ๋ฅ ๋ฌธ์ ๋ ์๋ชป๋ ๋์์ผ๋ก ์ด์ด์ง ์ ์์ผ๋ฏ๋ก ๋ฌธ์ ๊ฐ ๋ ์ ์์ต๋๋ค. ์ดํํธ๊ฐ ๋ค์ ์คํ๋๋ ์์ ์ ์ ์ดํ๊ธฐ ์ํด useEffect๋ ๋ ๋ฒ์งธ ์ธ์๋ก ์์กด์ฑ ๋ฐฐ์ด(dependency array)์ ๋ฐ์ต๋๋ค.
- ์์กด์ฑ ๋ฐฐ์ด์ด ์๋ต๋๋ฉด, ์ดํํธ๋ ๋ชจ๋ ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค.
- ๋น ๋ฐฐ์ด(
[])์ด ์ ๊ณต๋๋ฉด, ์ดํํธ๋ ์ด๊ธฐ ๋ ๋๋ง ํ ํ ๋ฒ๋ง ์คํ๋๋ฉฐ(componentDidMount์ ์ ์ฌ), ์ ๋ฆฌ(cleanup) ํจ์๋ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๋ ํ ๋ฒ ์คํ๋ฉ๋๋ค(componentWillUnmount์ ์ ์ฌ). - ์์กด์ฑ ๋ฐฐ์ด(
[dep1, dep2])์ด ์ ๊ณต๋๋ฉด, ์ดํํธ๋ ๋ ๋๋ง ๊ฐ์ ํด๋น ์์กด์ฑ ์ค ํ๋๋ผ๋ ๋ณ๊ฒฝ๋ ๋๋ง ๋ค์ ์คํ๋ฉ๋๋ค.
๋ค์ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
๋น์ ์ {count}๋ฒ ํด๋ฆญํ์ต๋๋ค
import React, { useEffect, useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// ์์กด์ฑ ๋ฐฐ์ด์ด ์์ผ๋ฉด ์ด ์ดํํธ๋ ๋ชจ๋ ๋ ๋๋ง ํ์ ์คํ๋ฉ๋๋ค
// ๋๋ [count]๊ฐ ์์กด์ฑ์ธ ๊ฒฝ์ฐ 'count'๊ฐ ๋ณ๊ฒฝ๋ ๋ ์คํ๋ฉ๋๋ค.
document.title = `Count: ${count}`;
// ๋ฐํ๋๋ ํจ์๊ฐ ๋ฐ๋ก ์ ๋ฆฌ(cleanup) ๋ฉ์ปค๋์ฆ์
๋๋ค
return () => {
// ์ด ํจ์๋ ์ดํํธ๊ฐ ๋ค์ ์คํ๋๊ธฐ ์ (์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ)
// ๊ทธ๋ฆฌ๊ณ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๋ ์คํ๋ฉ๋๋ค.
console.log('count ์ดํํธ์ ๋ํ ์ ๋ฆฌ');
};
}, [count]); // ์์กด์ฑ ๋ฐฐ์ด: count๊ฐ ๋ณ๊ฒฝ๋ ๋ ์ดํํธ๊ฐ ๋ค์ ์คํ๋ฉ๋๋ค
return (
"์ ๋ฆฌ(Cleanup)" ๋ถ๋ถ: ์ธ์ , ์ ์ค์ํ๊ฐ
useEffect์ ์ ๋ฆฌ ๋ฉ์ปค๋์ฆ์ ์ดํํธ ์ฝ๋ฐฑ์์ ๋ฐํ๋๋ ํจ์์
๋๋ค. ์ด ํจ์๋ ์ดํํธ์ ์ํด ํ ๋น๋ ๋ฆฌ์์ค๋ ์์๋ ์์
์ด ๋ ์ด์ ํ์ํ์ง ์์ ๋ ์ ์ ํ๊ฒ ํด์ ๋๊ฑฐ๋ ์ค์ง๋๋๋ก ๋ณด์ฅํ๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ ๋ฆฌ ํจ์๋ ์ฃผ๋ก ๋ค์ ๋ ๊ฐ์ง ์๋๋ฆฌ์ค์์ ์คํ๋ฉ๋๋ค:
- ์ดํํธ๊ฐ ๋ค์ ์คํ๋๊ธฐ ์ : ์ดํํธ์ ์์กด์ฑ์ด ์๊ณ ๊ทธ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋๋ฉด, ์ด์ ์ดํํธ ์คํ์ ์ ๋ฆฌ ํจ์๊ฐ ์ ์ดํํธ๊ฐ ์คํ๋๊ธฐ ์ ์ ๋จผ์ ์คํ๋ฉ๋๋ค. ์ด๋ ์ ์ดํํธ๊ฐ ๊นจ๋ํ ์ํ์์ ์์ํ๋๋ก ๋ณด์ฅํฉ๋๋ค.
- ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๋: ์ปดํฌ๋ํธ๊ฐ DOM์์ ์ ๊ฑฐ๋ ๋, ๋ง์ง๋ง ์ดํํธ ์คํ์ ์ ๋ฆฌ ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ์ด๋ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ ๊ธฐํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๋ ๋ฐ ํ์์ ์ ๋๋ค.
์ด ์ ๋ฆฌ๊ฐ ๊ธ๋ก๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ ๊ทธ๋ ๊ฒ ์ค์ํ ๊น์?
- ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง: ๊ตฌ๋ ํด์ง๋์ง ์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋, ์ ๋ฆฌ๋์ง ์์ ํ์ด๋จธ, ๋๋ ๋ซํ์ง ์์ ๋คํธ์ํฌ ์ฐ๊ฒฐ์ ๊ทธ๊ฒ๋ค์ ์์ฑํ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ํ์๋ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ์ ๋จ์ ์์ ์ ์์ต๋๋ค. ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ์ํ์ง ๋ฆฌ์์ค๋ค์ด ์ถ์ ๋์ด ์ฑ๋ฅ ์ ํ, ๋๋ ค์ง, ๊ทธ๋ฆฌ๊ณ ๊ฒฐ๊ตญ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ถฉ๋๋ก ์ด์ด์ง๋๋ค. ์ด๋ ์ ์ธ๊ณ ์ด๋ ์ฌ์ฉ์์๊ฒ๋ ์ข์ ์ค๋ฌ์ด ๊ฒฝํ์ ๋๋ค.
- ์์์น ๋ชปํ ๋์ ๋ฐ ๋ฒ๊ทธ ๋ฐฉ์ง: ์ ์ ํ ์ ๋ฆฌ ์์ด๋, ์ค๋๋ ์ดํํธ๊ฐ ์ค๋๋ ๋ฐ์ดํฐ(stale data)๋ก ๊ณ์ ์๋ํ๊ฑฐ๋ ์กด์ฌํ์ง ์๋ DOM ์์์ ์ํธ์์ฉํ์ฌ ๋ฐํ์ ์ค๋ฅ, ๋ถ์ ํํ UI ์ ๋ฐ์ดํธ, ์ฌ์ง์ด ๋ณด์ ์ทจ์ฝ์ ์ ์ ๋ฐํ ์ ์์ต๋๋ค. ๋ ์ด์ ๋ณด์ด์ง ์๋ ์ปดํฌ๋ํธ๋ฅผ ์ํด ๊ตฌ๋ ์ด ๊ณ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๋ถํ์ํ ๋คํธ์ํฌ ์์ฒญ์ด๋ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ ๋ฐํ๋ ์ํฉ์ ์์ํด ๋ณด์ธ์.
- ์ฑ๋ฅ ์ต์ ํ: ๋ฆฌ์์ค๋ฅผ ์ ์ํ๊ฒ ํด์ ํจ์ผ๋ก์จ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ณ๊ณ ํจ์จ์ ์ผ๋ก ์ ์งํ ์ ์์ต๋๋ค. ์ด๋ ์ฑ๋ฅ์ด ๋ฎ์ ๊ธฐ๊ธฐ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ๋คํธ์ํฌ ๋์ญํญ์ด ์ ํ์ ์ธ ์ฌ์ฉ์์๊ฒ ํนํ ์ค์ํ๋ฉฐ, ์ด๋ ์ธ๊ณ ์ฌ๋ฌ ์ง์ญ์์ ํํ ์๋๋ฆฌ์ค์ ๋๋ค.
- ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ณด์ฅ: ์ ๋ฆฌ๋ ์์ธก ๊ฐ๋ฅํ ์ํ๋ฅผ ์ ์งํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ปดํฌ๋ํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ ํ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ด๋ํ ๋, fetch ์์ ์ ์ ๋ฆฌํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ํ์ ๋์ฐฉํ๋ ์๋ต์ ์ฒ๋ฆฌํ๋ ค๋ ์๋๋ฅผ ๋ง์ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ปค์คํ ํ ์์ Effect ์ ๋ฆฌ๊ฐ ํ์ํ ์ผ๋ฐ์ ์ธ ์๋๋ฆฌ์ค
์ปค์คํ ํ ์ ์ํ ์ ์ฅ ๋ก์ง๊ณผ ์ฌ์ด๋ ์ดํํธ๋ฅผ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ก ์ถ์ํํ๋ React์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ๋๋ค. ์ปค์คํ ํ ์ ์ค๊ณํ ๋, ์ ๋ฆฌ๋ ๊ทธ ๊ฒฌ๊ณ ํจ์ ํ์์ ์ธ ๋ถ๋ถ์ด ๋ฉ๋๋ค. ์ดํํธ ์ ๋ฆฌ๊ฐ ์ ๋์ ์ผ๋ก ํ์ํ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์๋๋ฆฌ์ค ๋ช ๊ฐ์ง๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
1. ๊ตฌ๋ (์น์์ผ, ์ด๋ฒคํธ ์ด๋ฏธํฐ)
๋ง์ ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์๊ฐ ๋ฐ์ดํฐ๋ ํต์ ์ ์์กดํฉ๋๋ค. ์น์์ผ, ์๋ฒ-์ ์ก ์ด๋ฒคํธ(SSE), ๋๋ ์ปค์คํ ์ด๋ฒคํธ ์ด๋ฏธํฐ๊ฐ ๋ํ์ ์ธ ์์ ๋๋ค. ์ปดํฌ๋ํธ๊ฐ ์ด๋ฌํ ์คํธ๋ฆผ์ ๊ตฌ๋ ํ ๋, ์ปดํฌ๋ํธ๊ฐ ๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ ํ์๋ก ํ์ง ์์ ๋ ๊ตฌ๋ ์ ํด์งํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๊ตฌ๋ ์ด ๊ณ์ ํ์ฑ ์ํ๋ก ๋จ์ ๋ฆฌ์์ค๋ฅผ ์๋นํ๊ณ ์ ์ฌ์ ์ผ๋ก ์ค๋ฅ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค.
์์ : useWebSocket ์ปค์คํ
ํ
์ฐ๊ฒฐ ์ํ: {isConnected ? '์จ๋ผ์ธ' : '์คํ๋ผ์ธ'} ์ต์ ๋ฉ์์ง: {message}
import React, { useEffect, useState } from 'react';
function useWebSocket(url) {
const [message, setMessage] = useState(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('์น์์ผ ์ฐ๊ฒฐ๋จ');
setIsConnected(true);
};
ws.onmessage = (event) => {
console.log('๋ฉ์์ง ์์ :', event.data);
setMessage(event.data);
};
ws.onclose = () => {
console.log('์น์์ผ ์ฐ๊ฒฐ ๋๊น');
setIsConnected(false);
};
ws.onerror = (error) => {
console.error('์น์์ผ ์ค๋ฅ:', error);
setIsConnected(false);
};
// ์ ๋ฆฌ ํจ์
return () => {
if (ws.readyState === WebSocket.OPEN) {
console.log('์น์์ผ ์ฐ๊ฒฐ ๋ซ๋ ์ค');
ws.close();
}
};
}, [url]); // URL์ด ๋ณ๊ฒฝ๋๋ฉด ์ฌ์ฐ๊ฒฐ
return { message, isConnected };
}
// ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์:
function RealTimeDataDisplay() {
const { message, isConnected } = useWebSocket('wss://echo.websocket.events');
return (
์ค์๊ฐ ๋ฐ์ดํฐ ์ํ
์ด useWebSocket ํ
์์ ์ ๋ฆฌ ํจ์๋ ์ด ํ
์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋ ๋(์: ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ ๋) ์น์์ผ ์ฐ๊ฒฐ์ด ์ ์์ ์ผ๋ก ๋ซํ๋๋ก ๋ณด์ฅํฉ๋๋ค. ์ด๊ฒ์ด ์๋ค๋ฉด ์ฐ๊ฒฐ์ ๊ณ์ ์ด๋ ค ์๊ฒ ๋์ด ๋คํธ์ํฌ ๋ฆฌ์์ค๋ฅผ ์๋นํ๊ณ , UI์ ๋ ์ด์ ์กด์ฌํ์ง ์๋ ์ปดํฌ๋ํธ๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ ค๊ณ ์๋ํ ์ ์์ต๋๋ค.
2. ์ด๋ฒคํธ ๋ฆฌ์ค๋ (DOM, ์ ์ญ ๊ฐ์ฒด)
document, window ๋๋ ํน์ DOM ์์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ํํ ์ฌ์ด๋ ์ดํํธ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฌํ ๋ฆฌ์ค๋๋ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ณ ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์์ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋์ง ์๋๋ก ๋ฐ๋์ ์ ๊ฑฐํด์ผ ํฉ๋๋ค.
์์ : useClickOutside ์ปค์คํ
ํ
์ด ํ ์ ์ฐธ์กฐ๋ ์์ ์ธ๋ถ์ ํด๋ฆญ์ ๊ฐ์งํ๋ฉฐ, ๋๋กญ๋ค์ด, ๋ชจ๋ฌ ๋๋ ๋ด๋น๊ฒ์ด์ ๋ฉ๋ด์ ์ ์ฉํฉ๋๋ค.
์ด๊ฒ์ ๋ชจ๋ฌ ๋ํ ์์์
๋๋ค.
import React, { useEffect } from 'react';
function useClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
// ref์ ์์๋ ํ์ ์์๋ฅผ ํด๋ฆญํ๋ฉด ์๋ฌด๊ฒ๋ ํ์ง ์์
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
// ์ ๋ฆฌ ํจ์: ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]); // ref๋ handler๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ค์ ์คํ
}
// ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์:
function Modal() {
const modalRef = React.useRef();
const [isOpen, setIsOpen] = React.useState(true);
useClickOutside(modalRef, () => setIsOpen(false));
if (!isOpen) return null;
return (
๋ฐ๊นฅ์ ํด๋ฆญํ์ฌ ๋ซ๊ธฐ
์ฌ๊ธฐ์์ ์ ๋ฆฌ๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋ชจ๋ฌ์ด ๋ซํ๊ณ ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๋ฉด, mousedown๊ณผ touchstart ๋ฆฌ์ค๋๋ document์ ๊ณ์ ๋จ์ ์๊ฒ ๋์ด, ์ด์ ๋ ์กด์ฌํ์ง ์๋ ref.current์ ์ ๊ทผํ๋ ค๊ณ ํ ๋ ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๊ฑฐ๋ ์์์น ๋ชปํ ํธ๋ค๋ฌ ํธ์ถ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
3. ํ์ด๋จธ (setInterval, setTimeout)
ํ์ด๋จธ๋ ์ ๋๋ฉ์ด์ , ์นด์ดํธ๋ค์ด ๋๋ ์ฃผ๊ธฐ์ ์ธ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ์ ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค. ๊ด๋ฆฌ๋์ง ์๋ ํ์ด๋จธ๋ React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฉ๋ชจ๋ฆฌ ๋์์ ์์์น ๋ชปํ ๋์์ ๊ณ ์ ์ ์ธ ์์ธ์ ๋๋ค.
์์ : useInterval ์ปค์คํ
ํ
์ด ํ
์ ์ ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ ์ ์ธ์ ์ธ setInterval์ ์ ๊ณตํฉ๋๋ค.
import React, { useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// ์ต์ ์ฝ๋ฐฑ์ ๊ธฐ์ตํฉ๋๋ค.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// ์ธํฐ๋ฒ์ ์ค์ ํฉ๋๋ค.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
// ์ ๋ฆฌ ํจ์: ์ธํฐ๋ฒ์ ์ ๊ฑฐํฉ๋๋ค
return () => clearInterval(id);
}
}, [delay]);
}
// ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์:
function Counter() {
const [count, setCount] = React.useState(0);
useInterval(() => {
// ์ฌ๊ธฐ์ ์ปค์คํ
๋ก์ง์ ์์ฑํฉ๋๋ค
setCount(count + 1);
}, 1000); // 1์ด๋ง๋ค ์
๋ฐ์ดํธ
return ์นด์ดํฐ: {count}
;
}
์ฌ๊ธฐ์ ์ ๋ฆฌ ํจ์ clearInterval(id)๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋ง์ฝ Counter ์ปดํฌ๋ํธ๊ฐ ์ธํฐ๋ฒ์ ์ ๋ฆฌํ์ง ์๊ณ ์ธ๋ง์ดํธ๋๋ค๋ฉด, `setInterval` ์ฝ๋ฐฑ์ ๋งค์ด ๊ณ์ ์คํ๋์ด ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์ setCount๋ฅผ ํธ์ถํ๋ ค๊ณ ์๋ํ ๊ฒ์ด๊ณ , ์ด๋ React๊ฐ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ์์ํค๋ฉฐ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
4. ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๋ฐ AbortController
API ์์ฒญ ์์ฒด๋ ์ผ๋ฐ์ ์ผ๋ก ์๋ฃ๋ ์์
์ '๋๋๋ฆฌ๋' ์๋ฏธ์ '์ ๋ฆฌ'๋ฅผ ํ์๋ก ํ์ง ์์ง๋ง, ์งํ ์ค์ธ ์์ฒญ์ ๊ทธ๋ด ์ ์์ต๋๋ค. ๋ง์ฝ ์ปดํฌ๋ํธ๊ฐ ๋ฐ์ดํฐ fetch๋ฅผ ์์ํ๊ณ ์์ฒญ์ด ์๋ฃ๋๊ธฐ ์ ์ ์ธ๋ง์ดํธ๋๋ค๋ฉด, ํ๋ก๋ฏธ์ค๋ ์ฌ์ ํ resolve๋๊ฑฐ๋ reject๋ ์ ์์ผ๋ฉฐ, ์ด๋ ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ค๋ ์๋๋ก ์ด์ด์ง ์ ์์ต๋๋ค. AbortController๋ ๋ณด๋ฅ ์ค์ธ fetch ์์ฒญ์ ์ทจ์ํ๋ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค.
์์ : AbortController๋ฅผ ์ฌ์ฉํ useDataFetch ์ปค์คํ
ํ
์ฌ์ฉ์ ํ๋กํ ๋ก๋ฉ ์ค... ์ค๋ฅ: {error.message} ์ฌ์ฉ์ ๋ฐ์ดํฐ ์์. ์ด๋ฆ: {user.name} ์ด๋ฉ์ผ: {user.email}
import React, { useState, useEffect } from 'react';
function useDataFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP ์ค๋ฅ! ์ํ: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch๊ฐ ์ค๋จ๋์์ต๋๋ค');
} else {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
// ์ ๋ฆฌ ํจ์: fetch ์์ฒญ ์ค๋จ
return () => {
abortController.abort();
console.log('์ธ๋ง์ดํธ/์ฌ๋ ๋๋ง ์ ๋ฐ์ดํฐ fetch ์ค๋จ๋จ');
};
}, [url]); // URL์ด ๋ณ๊ฒฝ๋๋ฉด ๋ค์ ๊ฐ์ ธ์ค๊ธฐ
return { data, loading, error };
}
// ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์:
function UserProfile({ userId }) {
const { data: user, loading, error } = useDataFetch(`https://api.example.com/users/${userId}`);
if (loading) return ์ฌ์ฉ์ ํ๋กํ
์ ๋ฆฌ ํจ์์ ์๋ abortController.abort()๋ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋ง์ฝ UserProfile์ด fetch ์์ฒญ์ด ์งํ ์ค์ธ ๋์ ์ธ๋ง์ดํธ๋๋ฉด, ์ด ์ ๋ฆฌ๊ฐ ์์ฒญ์ ์ทจ์ํ ๊ฒ์
๋๋ค. ์ด๋ ๋ถํ์ํ ๋คํธ์ํฌ ํธ๋ํฝ์ ๋ฐฉ์งํ๊ณ , ๋ ์ค์ํ๊ฒ๋ ๋์ค์ ํ๋ก๋ฏธ์ค๊ฐ resolve๋์ด ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์ setData๋ setError๋ฅผ ํธ์ถํ๋ ค๋ ์๋๋ฅผ ๋ง์์ค๋๋ค.
5. DOM ์กฐ์ ๋ฐ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
DOM๊ณผ ์ง์ ์ํธ์์ฉํ๊ฑฐ๋ ์์ฒด DOM ์์๋ฅผ ๊ด๋ฆฌํ๋ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์ง๋ ์ปดํฌ๋ํธ)๋ฅผ ํตํฉํ ๋, ์ข ์ข ์ค์ ๋ฐ ํด์ ์์ ์ ์ํํด์ผ ํฉ๋๋ค.
์์ : ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด๊ธฐํ ๋ฐ ํ๊ธฐ (๊ฐ๋ ์ )
import React, { useEffect, useRef } from 'react';
// ChartLibrary๊ฐ Chart.js๋ D3 ๊ฐ์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ผ๊ณ ๊ฐ์
import ChartLibrary from 'chart-library';
function useChart(data, options) {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (chartRef.current) {
// ๋ง์ดํธ ์ ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด๊ธฐํ
chartInstance.current = new ChartLibrary(chartRef.current, { data, options });
}
// ์ ๋ฆฌ ํจ์: ์ฐจํธ ์ธ์คํด์ค ํ๊ธฐ
return () => {
if (chartInstance.current) {
chartInstance.current.destroy(); // ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ destroy ๋ฉ์๋๊ฐ ์๋ค๊ณ ๊ฐ์
chartInstance.current = null;
}
};
}, [data, options]); // ๋ฐ์ดํฐ๋ ์ต์
์ด ๋ณ๊ฒฝ๋๋ฉด ๋ค์ ์ด๊ธฐํ
return chartRef;
}
// ์ปดํฌ๋ํธ์์์ ์ฌ์ฉ ์:
function SalesChart({ salesData }) {
const chartContainerRef = useChart(salesData, { type: 'bar' });
return (
์ ๋ฆฌ ํจ์์ ์๋ chartInstance.current.destroy()๋ ํ์์ ์
๋๋ค. ์ด๊ฒ์ด ์์ผ๋ฉด, ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ DOM ์์, ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋๋ ๊ธฐํ ๋ด๋ถ ์ํ๋ฅผ ๋จ๊ฒจ๋์ด ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์ ๋ฐํ๊ณ , ๊ฐ์ ์์น์ ๋ค๋ฅธ ์ฐจํธ๊ฐ ์ด๊ธฐํ๋๊ฑฐ๋ ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง๋ ๋ ์ ์ฌ์ ์ธ ์ถฉ๋์ ์ผ์ผํฌ ์ ์์ต๋๋ค.
์ ๋ฆฌ(Cleanup) ๊ธฐ๋ฅ์ ๊ฐ์ถ ๊ฒฌ๊ณ ํ ์ปค์คํ ํ ๋ง๋ค๊ธฐ
์ปค์คํ ํ ์ ํ์ ๋ณต์กํ ๋ก์ง์ ์บก์ํํ์ฌ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ ํ ์คํธํ๊ธฐ ์ฝ๊ฒ ๋ง๋๋ ๋ฅ๋ ฅ์ ์์ต๋๋ค. ์ด๋ฌํ ํ ๋ด์์ ์ ๋ฆฌ๋ฅผ ์ ์ ํ๊ฒ ๊ด๋ฆฌํ๋ฉด ์ด ์บก์ํ๋ ๋ก์ง์ด ๊ฒฌ๊ณ ํ๊ณ ์ฌ์ด๋ ์ดํํธ ๊ด๋ จ ๋ฌธ์ ๊ฐ ์๋๋ก ๋ณด์ฅํ ์ ์์ต๋๋ค.
์ฒ ํ: ์บก์ํ์ ์ฌ์ฌ์ฉ์ฑ
์ปค์คํ
ํ
์ '๋ฐ๋ณตํ์ง ๋ง์ธ์'(Don't Repeat Yourself, DRY) ์์น์ ๋ฐ๋ฅผ ์ ์๊ฒ ํด์ค๋๋ค. ์ฌ๋ฌ ์ปดํฌ๋ํธ์ useEffect ํธ์ถ๊ณผ ํด๋น ์ ๋ฆฌ ๋ก์ง์ ํฉ์ด๋๋ ๋์ , ์ปค์คํ
ํ
์ ์ค์ ์ง์คํํ ์ ์์ต๋๋ค. ์ด๋ ์ฝ๋๋ฅผ ๋ ๊นจ๋ํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ฐ๋ฉฐ ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ์ ๊ฒ ๋ง๋ญ๋๋ค. ์ปค์คํ
ํ
์ด ์์ฒด ์ ๋ฆฌ๋ฅผ ์ฒ๋ฆฌํ๋ฉด, ํด๋น ํ
์ ์ฌ์ฉํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ ์๋์ผ๋ก ์ฑ
์๊ฐ ์๋ ๋ฆฌ์์ค ๊ด๋ฆฌ์ ์ด์ ์ ๋๋ฆฌ๊ฒ ๋ฉ๋๋ค.
์์ ์์ ๋ค์ ๊ฐ์ ํ๊ณ ํ์ฅํ๋ฉฐ, ๊ธ๋ก๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ฐ์กฐํด ๋ณด๊ฒ ์ต๋๋ค.
์์ 1: useWindowSize โ ์ ์ญ ๋ฐ์ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ํ
๋ฐ์ํ ๋์์ธ์ ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์ ์ฅ์น๋ฅผ ์์ฉํ๋ ๊ธ๋ก๋ฒ ์ฌ์ฉ์๋ฅผ ์ํด ํต์ฌ์ ์ ๋๋ค. ์ด ํ ์ ์ฐฝ ํฌ๊ธฐ๋ฅผ ์ถ์ ํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ฐฝ ๋๋น: {width}px ์ฐฝ ๋์ด: {height}px
ํ์ฌ ํ๋ฉด์ {width < 768 ? '์์ต๋๋ค' : 'ํฝ๋๋ค'}.
์ด๋ฌํ ์ ์์ฑ์ ์ ์ธ๊ณ์ ๋ค์ํ ๊ธฐ๊ธฐ ์ฌ์ฉ์์๊ฒ ๋งค์ฐ ์ค์ํฉ๋๋ค.
import React, { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
});
useEffect(() => {
// SSR ํ๊ฒฝ์ ์ํด window๊ฐ ์ ์๋์ด ์๋์ง ํ์ธ
if (typeof window === 'undefined') {
return;
}
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize);
// ์ ๋ฆฌ ํจ์: ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // ๋น ์์กด์ฑ ๋ฐฐ์ด์ ์ด ์ดํํธ๊ฐ ๋ง์ดํธ ์ ํ ๋ฒ ์คํ๋๊ณ ์ธ๋ง์ดํธ ์ ์ ๋ฆฌ๋จ์ ์๋ฏธ
return windowSize;
}
// ์ฌ์ฉ ์:
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
์ฌ๊ธฐ์ ๋น ์์กด์ฑ ๋ฐฐ์ด []๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋ ํ ๋ฒ ์ถ๊ฐ๋๊ณ ์ธ๋ง์ดํธ๋ ๋ ํ ๋ฒ ์ ๊ฑฐ๋จ์ ์๋ฏธํ๋ฉฐ, ์ฌ๋ฌ ๋ฆฌ์ค๋๊ฐ ๋ถ์ฐฉ๋๊ฑฐ๋ ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ผ์ง ํ์๋ ๋จ์ ์๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. typeof window !== 'undefined' ํ์ธ์ ์ด๊ธฐ ๋ก๋ ์๊ฐ๊ณผ SEO๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ํ๋ ์น ๊ฐ๋ฐ์์ ํํ ์ฌ์ฉ๋๋ ์๋ฒ-์ฌ์ด๋ ๋ ๋๋ง(SSR) ํ๊ฒฝ๊ณผ์ ํธํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
์์ 2: useOnlineStatus โ ์ ์ญ ๋คํธ์ํฌ ์ํ ๊ด๋ฆฌ
๋คํธ์ํฌ ์ฐ๊ฒฐ์ ์์กดํ๋ ์ ํ๋ฆฌ์ผ์ด์ (์: ์ค์๊ฐ ํ์ ๋๊ตฌ, ๋ฐ์ดํฐ ๋๊ธฐํ ์ฑ)์ ๊ฒฝ์ฐ, ์ฌ์ฉ์์ ์จ๋ผ์ธ ์ํ๋ฅผ ์๋ ๊ฒ์ด ํ์์ ์ ๋๋ค. ์ด ํ ์ ์ ์ ํ ์ ๋ฆฌ์ ํจ๊ป ์ด๋ฅผ ์ถ์ ํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
๋คํธ์ํฌ ์ํ: {isOnline ? '์ฐ๊ฒฐ๋จ' : '์ฐ๊ฒฐ ๋๊น'}.
์ด๋ ์ธํฐ๋ท ์ฐ๊ฒฐ์ด ๋ถ์์ ํ ์ง์ญ์ ์ฌ์ฉ์์๊ฒ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค.
import React, { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true);
useEffect(() => {
// SSR ํ๊ฒฝ์ ์ํด navigator๊ฐ ์ ์๋์ด ์๋์ง ํ์ธ
if (typeof navigator === 'undefined') {
return;
}
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
// ์ ๋ฆฌ ํจ์: ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []); // ๋ง์ดํธ ์ ํ ๋ฒ ์คํ, ์ธ๋ง์ดํธ ์ ์ ๋ฆฌ
return isOnline;
}
// ์ฌ์ฉ ์:
function NetworkStatusIndicator() {
const isOnline = useOnlineStatus();
return (
useWindowSize์ ์ ์ฌํ๊ฒ, ์ด ํ
์ window ๊ฐ์ฒด์ ์ ์ญ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ถ๊ฐํ๊ณ ์ ๊ฑฐํฉ๋๋ค. ์ ๋ฆฌ ์์ด๋ ์ด๋ฌํ ๋ฆฌ์ค๋๊ฐ ๊ณ์ ๋จ์ ์ธ๋ง์ดํธ๋ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๊ณ์ ์
๋ฐ์ดํธํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์์ ์ฝ์ ๊ฒฝ๊ณ ๋ฅผ ์ ๋ฐํ ๊ฒ์
๋๋ค. navigator์ ๋ํ ์ด๊ธฐ ์ํ ํ์ธ์ SSR ํธํ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
์์ 3: useKeyPress โ ์ ๊ทผ์ฑ์ ์ํ ๊ณ ๊ธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๊ด๋ฆฌ
์ํธ์์ฉ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ์ข ํค๋ณด๋ ์ ๋ ฅ์ ์๊ตฌํฉ๋๋ค. ์ด ํ ์ ํน์ ํค ๋๋ฆ์ ๊ฐ์งํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฉฐ, ์ด๋ ์ ์ธ๊ณ์ ์ผ๋ก ์ ๊ทผ์ฑ๊ณผ ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ๋งค์ฐ ์ค์ํฉ๋๋ค.
์คํ์ด์ค๋ฐ๋ฅผ ๋๋ฅด์ธ์: {isSpacePressed ? '๋๋ฆผ!' : '๋ผ์ด์ง'} Enter๋ฅผ ๋๋ฅด์ธ์: {isEnterPressed ? '๋๋ฆผ!' : '๋ผ์ด์ง'} ํค๋ณด๋ ๋ด๋น๊ฒ์ด์
์ ํจ์จ์ ์ธ ์ํธ์์ฉ์ ์ํ ๊ธ๋ก๋ฒ ํ์ค์
๋๋ค.
import React, { useState, useEffect } from 'react';
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
useEffect(() => {
const downHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(true);
}
};
const upHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
// ์ ๋ฆฌ ํจ์: ๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ชจ๋ ์ ๊ฑฐ
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, [targetKey]); // targetKey๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ค์ ์คํ
return keyPressed;
}
// ์ฌ์ฉ ์:
function KeyboardListener() {
const isSpacePressed = useKeyPress(' ');
const isEnterPressed = useKeyPress('Enter');
return (
์ฌ๊ธฐ์ ์ ๋ฆฌ ํจ์๋ keydown๊ณผ keyup ๋ฆฌ์ค๋๋ฅผ ๋ชจ๋ ์ ์คํ๊ฒ ์ ๊ฑฐํ์ฌ ๋จ์ ์์ง ์๋๋ก ํฉ๋๋ค. targetKey ์์กด์ฑ์ด ๋ณ๊ฒฝ๋๋ฉด, ์ด์ ํค์ ๋ํ ๋ฆฌ์ค๋๋ ์ ๊ฑฐ๋๊ณ ์ ํค์ ๋ํ ์ ๋ฆฌ์ค๋๊ฐ ์ถ๊ฐ๋์ด ๊ด๋ จ ์๋ ๋ฆฌ์ค๋๋ง ํ์ฑ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
์์ 4: useInterval โ `useRef`๋ฅผ ํ์ฉํ ๊ฒฌ๊ณ ํ ํ์ด๋จธ ๊ด๋ฆฌ ํ
์์ useInterval์ ๋ณด์์ต๋๋ค. ์ด์ useRef๊ฐ ์ดํํธ์์ ํ์ด๋จธ ์ฌ์ฉ ์ ํํ ๋ฌธ์ ์ธ ์ค๋๋ ํด๋ก์ (stale closure)๋ฅผ ์ด๋ป๊ฒ ๋ฐฉ์งํ๋์ง ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ ํํ ํ์ด๋จธ๋ ๊ฒ์์์๋ถํฐ ์ฐ์
์ ์ดํ์ ์ด๋ฅด๊ธฐ๊น์ง ๋ง์ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ธฐ๋ณธ์
๋๋ค.
import React, { useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// ์ต์ ์ฝ๋ฐฑ์ ๊ธฐ์ตํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด 'callback' ์์ฒด๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ ์ปดํฌ๋ํธ ์ํ์ ์์กดํ๋๋ผ๋
// ํญ์ ์ต์ 'callback' ํจ์๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
// ์ด ์ดํํธ๋ 'callback' ์์ฒด๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง(์: 'useCallback' ๋๋ฌธ์) ๋ค์ ์คํ๋ฉ๋๋ค.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// ์ธํฐ๋ฒ์ ์ค์ ํฉ๋๋ค. ์ด ์ดํํธ๋ 'delay'๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ค์ ์คํ๋ฉ๋๋ค.
useEffect(() => {
function tick() {
// ref์์ ์ต์ ์ฝ๋ฐฑ์ ์ฌ์ฉ
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]); // delay๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ธํฐ๋ฒ ์ค์ ์ ๋ค์ ์คํ
}
// ์ฌ์ฉ ์:
function Stopwatch() {
const [seconds, setSeconds] = React.useState(0);
const [isRunning, setIsRunning] = React.useState(false);
useInterval(
() => {
if (isRunning) {
setSeconds((prevSeconds) => prevSeconds + 1);
}
},
isRunning ? 1000 : null // ์คํ ์ค์ด ์๋ ๋๋ delay๋ฅผ null๋ก ํ์ฌ ์ธํฐ๋ฒ์ ์ผ์ ์ค์ง
);
return (
์คํฑ์์น: {seconds} ์ด
savedCallback์ useRef๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ค์ํ ํจํด์
๋๋ค. ์ด๊ฒ์ด ์๋ค๋ฉด, callback(์: setCount(count + 1)๋ฅผ ์ฌ์ฉํ์ฌ ์นด์ดํฐ๋ฅผ ์ฆ๊ฐ์ํค๋ ํจ์)์ด ๋ ๋ฒ์งธ useEffect์ ์์กด์ฑ ๋ฐฐ์ด์ ์ง์ ํฌํจ๋ ๊ฒฝ์ฐ, count๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ธํฐ๋ฒ์ด ์ ๋ฆฌ๋๊ณ ์ฌ์ค์ ๋์ด ์ ๋ขฐํ ์ ์๋ ํ์ด๋จธ๊ฐ ๋ ๊ฒ์
๋๋ค. ์ต์ ์ฝ๋ฐฑ์ ref์ ์ ์ฅํจ์ผ๋ก์จ, ์ธํฐ๋ฒ ์์ฒด๋ delay๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ฌ์ค์ ํ๋ฉด ๋๊ณ , `tick` ํจ์๋ ํญ์ ์ต์ ๋ฒ์ ์ `callback` ํจ์๋ฅผ ํธ์ถํ์ฌ ์ค๋๋ ํด๋ก์ ๋ฅผ ํผํ ์ ์์ต๋๋ค.
์์ 5: useDebounce โ ํ์ด๋จธ์ ์ ๋ฆฌ(Cleanup)๋ฅผ ํตํ ์ฑ๋ฅ ์ต์ ํ
๋๋ฐ์ด์ฑ์ ํจ์ ํธ์ถ ์๋๋ฅผ ์ ํํ๋ ์ผ๋ฐ์ ์ธ ๊ธฐ์ ๋ก, ๊ฒ์ ์ ๋ ฅ์ด๋ ๋น์ฉ์ด ๋ง์ด ๋๋ ๊ณ์ฐ์ ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค. ์ฌ๋ฌ ํ์ด๋จธ๊ฐ ๋์์ ์คํ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ๊ธฐ์๋ ์ ๋ฆฌ๊ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค.
ํ์ฌ ๊ฒ์์ด: {searchTerm} ๋๋ฐ์ด์ค๋ ๊ฒ์์ด (API ํธ์ถ์ ์ฌ์ฉ๋ ๊ฐ๋ฅ์ฑ ๋์): {debouncedSearchTerm} ์ฌ์ฉ์ ์
๋ ฅ ์ต์ ํ๋ ํนํ ๋ค์ํ ๋คํธ์ํฌ ์กฐ๊ฑด์์ ์ํํ ์ํธ์์ฉ์ ์ํด ๋งค์ฐ ์ค์ํฉ๋๋ค.
import React, { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// ๋๋ฐ์ด์ค๋ ๊ฐ์ ์
๋ฐ์ดํธํ๊ธฐ ์ํด ํ์์์ ์ค์
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// ์ ๋ฆฌ ํจ์: ํ์์์์ด ์คํ๋๊ธฐ ์ ์ value๋ delay๊ฐ ๋ณ๊ฒฝ๋๋ฉด ํ์์์์ ์ ๊ฑฐ
return () => {
clearTimeout(handler);
};
}, [value, delay]); // value๋ delay๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ดํํธ ์ฌํธ์ถ
return debouncedValue;
}
// ์ฌ์ฉ ์:
function SearchBar() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms๋ก ๋๋ฐ์ด์ค
useEffect(() => {
if (debouncedSearchTerm) {
console.log('๊ฒ์ ์ค:', debouncedSearchTerm);
// ์ค์ ์ฑ์์๋ ์ฌ๊ธฐ์ API ํธ์ถ์ ๋ณด๋
๋๋ค
}
}, [debouncedSearchTerm]);
return (
์ ๋ฆฌ ํจ์์ clearTimeout(handler)๋ ์ฌ์ฉ์๊ฐ ๋น ๋ฅด๊ฒ ์
๋ ฅํ ๊ฒฝ์ฐ ์ด์ ์ ๋ณด๋ฅ ์ค์ธ ํ์์์์ด ์ทจ์๋๋๋ก ๋ณด์ฅํฉ๋๋ค. delay ๊ธฐ๊ฐ ๋ด์ ๋ง์ง๋ง ์
๋ ฅ๋ง์ด setDebouncedValue๋ฅผ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค. ์ด๋ API ํธ์ถ๊ณผ ๊ฐ์ ๋น์ฉ์ด ๋ง์ด ๋๋ ์์
์ ๊ณผ๋ถํ๋ฅผ ๋ฐฉ์งํ๊ณ ์ ํ๋ฆฌ์ผ์ด์
์๋ต์ฑ์ ํฅ์์์ผ ์ ์ธ๊ณ ์ฌ์ฉ์์๊ฒ ํฐ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
๊ณ ๊ธ ์ ๋ฆฌ ํจํด ๋ฐ ๊ณ ๋ ค ์ฌํญ
์ดํํธ ์ ๋ฆฌ์ ๊ธฐ๋ณธ ์์น์ ๊ฐ๋จํ์ง๋ง, ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ข ์ข ๋ ๋ฏธ๋ฌํ ๋์ ์ ์ง๋ฉดํฉ๋๋ค. ๊ณ ๊ธ ํจํด๊ณผ ๊ณ ๋ ค ์ฌํญ์ ์ดํดํ๋ฉด ์ปค์คํ ํ ์ด ๊ฒฌ๊ณ ํ๊ณ ์ ์๋ ฅ์ด ์๋๋ก ๋ณด์ฅํ ์ ์์ต๋๋ค.
์์กด์ฑ ๋ฐฐ์ด ์ดํดํ๊ธฐ: ์๋ ์ ๊ฒ
์์กด์ฑ ๋ฐฐ์ด์ ์ดํํธ๊ฐ ์ธ์ ์คํ๋ ์ง๋ฅผ ๊ฒฐ์ ํ๋ ๋ฌธ์ง๊ธฐ์ ๋๋ค. ์ด๋ฅผ ์๋ชป ๊ด๋ฆฌํ๋ฉด ๋ ๊ฐ์ง ์ฃผ์ ๋ฌธ์ ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
- ์์กด์ฑ ๋๋ฝ: ์ดํํธ ๋ด๋ถ์์ ์ฌ์ฉํ๋ ๊ฐ์ ์์กด์ฑ ๋ฐฐ์ด์ ํฌํจํ๋ ๊ฒ์ ์์ผ๋ฉด, ์ดํํธ๊ฐ "์ค๋๋(stale)" ํด๋ก์ ๋ก ์คํ๋ ์ ์์ต๋๋ค. ์ฆ, ์ค๋๋ ๋ฒ์ ์ ์ํ๋ props๋ฅผ ์ฐธ์กฐํ๊ฒ ๋ฉ๋๋ค. ์ด๋ ๋ฏธ๋ฌํ ๋ฒ๊ทธ์ ์๋ชป๋ ๋์์ผ๋ก ์ด์ด์ง ์ ์์ผ๋ฉฐ, ์ดํํธ(์ ๊ทธ ์ ๋ฆฌ ํจ์)๊ฐ ์ค๋๋ ์ ๋ณด๋ก ์๋ํ ์ ์์ต๋๋ค. React ESLint ํ๋ฌ๊ทธ์ธ์ ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ์ก๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
- ๊ณผ๋ํ ์์กด์ฑ ์ง์ : ๋ถํ์ํ ์์กด์ฑ, ํนํ ๋ชจ๋ ๋ ๋๋ง์์ ๋ค์ ์์ฑ๋๋ ๊ฐ์ฒด๋ ํจ์๋ฅผ ํฌํจํ๋ฉด ์ดํํธ๊ฐ ๋๋ฌด ์์ฃผ ๋ค์ ์คํ(๋ฐ๋ผ์ ๋ค์ ์ ๋ฆฌ๋๊ณ ๋ค์ ์ค์ )๋ ์ ์์ต๋๋ค. ์ด๋ ์ฑ๋ฅ ์ ํ, ๊น๋ฐ์ด๋ UI ๋ฐ ๋นํจ์จ์ ์ธ ๋ฆฌ์์ค ๊ด๋ฆฌ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
์์กด์ฑ์ ์์ ํํ๋ ค๋ฉด, ํจ์์๋ useCallback์ ์ฌ์ฉํ๊ณ , ์ฌ๊ณ์ฐ ๋น์ฉ์ด ๋น์ผ ๊ฐ์ฒด๋ ๊ฐ์๋ useMemo๋ฅผ ์ฌ์ฉํ์ธ์. ์ด ํ
๋ค์ ๊ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
ํ์ฌ, ์์กด์ฑ์ด ์ค์ง์ ์ผ๋ก ๋ณ๊ฒฝ๋์ง ์์์ ๋ ์์ ์ปดํฌ๋ํธ์ ๋ถํ์ํ ์ฌ๋ ๋๋ง์ด๋ ์ดํํธ์ ์ฌ์คํ์ ๋ฐฉ์งํฉ๋๋ค.
์นด์ดํธ: {count} ์ด๋ ์ ์คํ ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
import React, { useEffect, useState, useCallback, useMemo } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [filter, setFilter] = useState('');
// useEffect๊ฐ ๋ถํ์ํ๊ฒ ์ฌ์คํ๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
const fetchData = useCallback(async () => {
console.log('ํํฐ๋ก ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ์ค:', filter);
// ์ฌ๊ธฐ์ API ํธ์ถ์ด ์๋ค๊ณ ์์ํด๋ณด์ธ์
return `count ${count}์์ ${filter}์ ๋ํ ๋ฐ์ดํฐ`;
}, [filter, count]); // fetchData๋ filter๋ count๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ณ๊ฒฝ๋จ
// ๋ถํ์ํ ์ฌ๋ ๋๋ง/์ดํํธ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์์กด์ฑ์ผ๋ก ์ฌ์ฉ๋๋ ๊ฐ์ฒด๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
const complexOptions = useMemo(() => ({
retryAttempts: 3,
timeout: 5000
}), []); // ๋น ์์กด์ฑ ๋ฐฐ์ด์ options ๊ฐ์ฒด๊ฐ ํ ๋ฒ๋ง ์์ฑ๋จ์ ์๋ฏธ
useEffect(() => {
let isActive = true;
fetchData().then(data => {
if (isActive) {
console.log('์์ :', data);
}
});
return () => {
isActive = false;
console.log('fetch ์ดํํธ์ ๋ํ ์ ๋ฆฌ.');
};
}, [fetchData, complexOptions]); // ์ด์ ์ด ์ดํํธ๋ fetchData๋ complexOptions๊ฐ ์ ๋ง๋ก ๋ณ๊ฒฝ๋ ๋๋ง ์คํ๋จ
return (
`useRef`๋ก ์ค๋๋ ํด๋ก์ (Stale Closure) ์ฒ๋ฆฌํ๊ธฐ
์ฐ๋ฆฌ๋ useRef๊ฐ ๋ ๋๋ง์ ํธ๋ฆฌ๊ฑฐํ์ง ์์ผ๋ฉด์ ๋ ๋๋ง ๊ฐ์ ์ง์๋๋ ๊ฐ๋ณ ๊ฐ์ ์ ์ฅํ ์ ์๋ค๋ ๊ฒ์ ๋ณด์์ต๋๋ค. ์ด๋ ์ ๋ฆฌ ํจ์(๋๋ ์ดํํธ ์์ฒด)๊ฐ prop์ด๋ ์ํ์ *์ต์ * ๋ฒ์ ์ ์ ๊ทผํด์ผ ํ์ง๋ง, ํด๋น prop/์ํ๋ฅผ ์์กด์ฑ ๋ฐฐ์ด์ ํฌํจํ๊ณ ์ถ์ง ์์ ๋(์ดํํธ๊ฐ ๋๋ฌด ์์ฃผ ์ฌ์คํ๋๋ฏ๋ก) ํนํ ์ ์ฉํฉ๋๋ค.
2์ด ํ์ ๋ฉ์์ง๋ฅผ ๊ธฐ๋กํ๋ ์ดํํธ๋ฅผ ์๊ฐํด ๋ณด์ธ์. `count`๊ฐ ๋ณ๊ฒฝ๋๋ฉด, ์ ๋ฆฌ ํจ์๋ *์ต์ * count๊ฐ ํ์ํฉ๋๋ค.
ํ์ฌ ์นด์ดํธ: {count} 2์ด ํ์ ์ ๋ฆฌ ์์ count ๊ฐ์ ์ฝ์์์ ๊ด์ฐฐํ์ธ์.
import React, { useEffect, useState, useRef } from 'react';
function DelayedLogger() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
// ref๋ฅผ ์ต์ count๋ก ๊ณ์ ์
๋ฐ์ดํธ
useEffect(() => {
latestCount.current = count;
}, [count]);
useEffect(() => {
const timeoutId = setTimeout(() => {
// ์ด๊ฒ์ ํญ์ ํ์์์์ด ์ค์ ๋์์ ๋์ ํ์ฌ count ๊ฐ์ ๊ธฐ๋กํฉ๋๋ค
console.log(`์ดํํธ ์ฝ๋ฐฑ: Count๋ ${count}์์ต๋๋ค`);
// ์ด๊ฒ์ useRef ๋๋ถ์ ํญ์ ์ต์ count ๊ฐ์ ๊ธฐ๋กํฉ๋๋ค
console.log(`ref๋ฅผ ํตํ ์ดํํธ ์ฝ๋ฐฑ: ์ต์ count๋ ${latestCount.current}์
๋๋ค`);
}, 2000);
return () => {
clearTimeout(timeoutId);
// ์ด ์ ๋ฆฌ ํจ์๋ latestCount.current์๋ ์ ๊ทผํ ์ ์์ต๋๋ค
console.log(`์ ๋ฆฌ: ์ ๋ฆฌ ์์ ์ ์ต์ count๋ ${latestCount.current}์์ต๋๋ค`);
};
}, []); // ๋น ์์กด์ฑ ๋ฐฐ์ด, ์ดํํธ๋ ํ ๋ฒ๋ง ์คํ๋จ
return (
DelayedLogger๊ฐ ์ฒ์ ๋ ๋๋ง๋ ๋, ๋น ์์กด์ฑ ๋ฐฐ์ด์ ๊ฐ์ง `useEffect`๊ฐ ์คํ๋ฉ๋๋ค. `setTimeout`์ด ์์ฝ๋ฉ๋๋ค. 2์ด๊ฐ ์ง๋๊ธฐ ์ ์ count๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฆ๊ฐ์ํค๋ฉด, `latestCount.current`๋ ์ฒซ ๋ฒ์งธ `useEffect`(๋ชจ๋ `count` ๋ณ๊ฒฝ ํ์ ์คํ๋จ)๋ฅผ ํตํด ์
๋ฐ์ดํธ๋ฉ๋๋ค. `setTimeout`์ด ๋ง์นจ๋ด ์คํ๋ ๋, ํด๋ก์ ์ ์๋ `count`(์ดํํธ๊ฐ ์คํ๋ ๋น์์ count)์ ์ ๊ทผํ์ง๋ง, ํ์ฌ ref์ `latestCount.current`์ ์ ๊ทผํ์ฌ ๊ฐ์ฅ ์ต๊ทผ์ ์ํ๋ฅผ ๋ฐ์ํฉ๋๋ค. ์ด ์ฐจ์ด์ ์ ๊ฒฌ๊ณ ํ ์ดํํธ๋ฅผ ์ํด ๋งค์ฐ ์ค์ํฉ๋๋ค.
ํ ์ปดํฌ๋ํธ ๋ด ์ฌ๋ฌ Effect ์ฌ์ฉ vs. ์ปค์คํ ํ
๋จ์ผ ์ปดํฌ๋ํธ ๋ด์ ์ฌ๋ฌ useEffect ํธ์ถ์ ๋๋ ๊ฒ์ ์๋ฒฝํ๊ฒ ํ์ฉ๋ฉ๋๋ค. ์ฌ์ค, ๊ฐ ์ดํํธ๊ฐ ๋ณ๊ฐ์ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๊ด๋ฆฌํ ๋ ๊ถ์ฅ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ํ useEffect๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๋ค๋ฅธ ํ๋๋ ์น์์ผ ์ฐ๊ฒฐ์ ๊ด๋ฆฌํ๋ฉฐ, ์ธ ๋ฒ์งธ๋ ์ ์ญ ์ด๋ฒคํธ๋ฅผ ์์ ํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ์ด๋ฌํ ๋ณ๊ฐ์ ์ดํํธ๊ฐ ๋ณต์กํด์ง๊ฑฐ๋ ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ๊ฑธ์ณ ๋์ผํ ์ดํํธ ๋ก์ง์ ์ฌ์ฌ์ฉํ๋ ์์ ์ ๋ฐ๊ฒฌํ๋ค๋ฉด, ์ด๋ ํด๋น ๋ก์ง์ ์ปค์คํ ํ ์ผ๋ก ์ถ์ํํด์ผ ํ๋ค๋ ๊ฐ๋ ฅํ ์ ํธ์ ๋๋ค. ์ปค์คํ ํ ์ ๋ชจ๋์ฑ, ์ฌ์ฌ์ฉ์ฑ ๋ฐ ์ฌ์ด ํ ์คํธ๋ฅผ ์ด์งํ์ฌ, ๋๊ท๋ชจ ํ๋ก์ ํธ์ ๋ค์ํ ๊ฐ๋ฐํ์ ์ํด ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ณ ํ์ฅ ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค.
Effect ๋ด ์ค๋ฅ ์ฒ๋ฆฌ
์ฌ์ด๋ ์ดํํธ๋ ์คํจํ ์ ์์ต๋๋ค. API ํธ์ถ์ ์ค๋ฅ๋ฅผ ๋ฐํํ ์ ์๊ณ , ์น์์ผ ์ฐ๊ฒฐ์ ๋์ด์ง ์ ์์ผ๋ฉฐ, ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ธ๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. ์ปค์คํ ํ ์ ์ด๋ฌํ ์๋๋ฆฌ์ค๋ฅผ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
- ์ํ ๊ด๋ฆฌ: ์ค๋ฅ ์ํ๋ฅผ ๋ฐ์ํ๊ธฐ ์ํด ๋ก์ปฌ ์ํ(์:
setError(true))๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ์ปดํฌ๋ํธ๊ฐ ์ค๋ฅ ๋ฉ์์ง๋ ๋์ฒด UI๋ฅผ ๋ ๋๋งํ ์ ์๋๋ก ํฉ๋๋ค. - ๋ก๊น
:
console.error()๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ ์ญ ์ค๋ฅ ๋ก๊น ์๋น์ค์ ํตํฉํ์ฌ ๋ฌธ์ ๋ฅผ ์บก์ฒํ๊ณ ๋ณด๊ณ ํ์ธ์. ์ด๋ ๋ค์ํ ํ๊ฒฝ๊ณผ ์ฌ์ฉ์ ๊ธฐ๋ฐ์ ๊ฑธ์ณ ๋๋ฒ๊น ํ๋ ๋ฐ ๋งค์ฐ ์ ์ฉํฉ๋๋ค. - ์ฌ์๋ ๋ฉ์ปค๋์ฆ: ๋คํธ์ํฌ ์์ ์ ๊ฒฝ์ฐ, ์ผ์์ ์ธ ๋คํธ์ํฌ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ํ ๋ด์ ์ฌ์๋ ๋ก์ง(์ ์ ํ ์ง์ ๋ฐฑ์คํ ํฌํจ)์ ๊ตฌํํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์. ์ด๋ ์ธํฐ๋ท ์ ์์ด ๋ ์์ ์ ์ธ ์ง์ญ์ ์ฌ์ฉ์์๊ฒ ๋ณต์๋ ฅ์ ํฅ์์ํต๋๋ค.
๋ธ๋ก๊ทธ ํฌ์คํธ ๋ก๋ฉ ์ค... (์ฌ์๋: {retries}) ์ค๋ฅ: {error.message} {retries < 3 && '๊ณง ์ฌ์๋ํฉ๋๋ค...'} ๋ธ๋ก๊ทธ ํฌ์คํธ ๋ฐ์ดํฐ ์์. {post.author} {post.content}
import React, { useState, useEffect } from 'react';
function useReliableDataFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [retries, setRetries] = useState(0);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
let timeoutId;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
if (response.status === 404) {
throw new Error('๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.');
} else if (response.status >= 500) {
throw new Error('์๋ฒ ์ค๋ฅ, ๋ค์ ์๋ํด์ฃผ์ธ์.');
} else {
throw new Error(`HTTP ์ค๋ฅ! ์ํ: ${response.status}`);
}
}
const result = await response.json();
setData(result);
setRetries(0); // ์ฑ๊ณต ์ ์ฌ์๋ ํ์ ์ด๊ธฐํ
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch๊ฐ ์๋์ ์ผ๋ก ์ค๋จ๋์์ต๋๋ค');
} else {
console.error('Fetch ์ค๋ฅ:', err);
setError(err);
// ํน์ ์ค๋ฅ๋ ์ฌ์๋ ํ์์ ๋ํ ์ฌ์๋ ๋ก์ง ๊ตฌํ
if (retries < 3) { // ์ต๋ 3๋ฒ ์ฌ์๋
timeoutId = setTimeout(() => {
setRetries(prev => prev + 1);
}, Math.pow(2, retries) * 1000); // ์ง์ ๋ฐฑ์คํ (1์ด, 2์ด, 4์ด)
}
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortController.abort();
clearTimeout(timeoutId); // ์ธ๋ง์ดํธ/์ฌ๋ ๋๋ง ์ ์ฌ์๋ ํ์์์ ์ ๊ฑฐ
};
}, [url, retries]); // URL ๋ณ๊ฒฝ ๋๋ ์ฌ์๋ ์ ์ฌ์คํ
return { data, loading, error, retries };
}
// ์ฌ์ฉ ์:
function BlogPost({ postId }) {
const { data: post, loading, error, retries } = useReliableDataFetch(`https://api.example.com/posts/${postId}`);
if (loading) return {post.title}
์ด ํฅ์๋ ํ ์ ์ฌ์๋ ํ์์์์ ์ ๊ฑฐํ๋ ์ ๊ทน์ ์ธ ์ ๋ฆฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉฐ, ๊ฒฌ๊ณ ํ ์ค๋ฅ ์ฒ๋ฆฌ์ ๊ฐ๋จํ ์ฌ์๋ ๋ฉ์ปค๋์ฆ์ ์ถ๊ฐํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ผ์์ ์ธ ๋คํธ์ํฌ ๋ฌธ์ ๋ ๋ฐฑ์๋ ๊ฒฐํจ์ ๋ ํ๋ ฅ์ ์ผ๋ก ๋์ํ๋๋ก ๋ง๋ค์ด ์ ์ธ๊ณ์ ์ผ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํต๋๋ค.
์ ๋ฆฌ(Cleanup) ๊ธฐ๋ฅ์ด ์๋ ์ปค์คํ ํ ํ ์คํธํ๊ธฐ
์ฒ ์ ํ ํ ์คํธ๋ ๋ชจ๋ ์ํํธ์จ์ด, ํนํ ์ปค์คํ ํ ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ก์ง์ ์์ด ๊ฐ์ฅ ์ค์ํฉ๋๋ค. ์ฌ์ด๋ ์ดํํธ์ ์ ๋ฆฌ๊ฐ ์๋ ํ ์ ํ ์คํธํ ๋, ๋ค์์ ํ์ธํด์ผ ํฉ๋๋ค:
- ์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๋ ์ดํํธ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์คํ๋๋์ง.
- ์ดํํธ๊ฐ ๋ค์ ์คํ๋๊ธฐ ์ ์ ์ ๋ฆฌ ํจ์๊ฐ ํธ์ถ๋๋์ง(์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ).
- ์ปดํฌ๋ํธ(๋๋ ํ ์ ์๋น์)๊ฐ ์ธ๋ง์ดํธ๋ ๋ ์ ๋ฆฌ ํจ์๊ฐ ํธ์ถ๋๋์ง.
- ๋ฆฌ์์ค๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ํด์ ๋๋์ง(์: ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ ๊ฑฐ, ํ์ด๋จธ ์ ๋ฆฌ).
@testing-library/react-hooks(๋๋ ์ปดํฌ๋ํธ ์์ค ํ
์คํธ๋ฅผ ์ํ @testing-library/react)์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ
์ ๊ฒฉ๋ฆฌํ์ฌ ํ
์คํธํ ์ ์๋ ์ ํธ๋ฆฌํฐ๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ฌ๊ธฐ์๋ ์ฌ๋ ๋๋ง ๋ฐ ์ธ๋ง์ดํ
์ ์๋ฎฌ๋ ์ด์
ํ๋ ๋ฉ์๋๊ฐ ํฌํจ๋์ด ์์ด ์ ๋ฆฌ ํจ์๊ฐ ์์๋๋ก ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
์ปค์คํ ํ ์์ Effect ์ ๋ฆฌ๋ฅผ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
์์ฝํ์๋ฉด, React ์ปค์คํ ํ ์์ ์ดํํธ ์ ๋ฆฌ๋ฅผ ๋ง์คํฐํ์ฌ ๋ชจ๋ ๋๋ฅ๊ณผ ์ฅ์น์ ์ฌ์ฉ์์๊ฒ ๊ฒฌ๊ณ ํ๊ณ ์ฑ๋ฅ์ด ๋ฐ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์ฅํ๊ธฐ ์ํ ํ์์ ์ธ ๋ชจ๋ฒ ์ฌ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
-
ํญ์ ์ ๋ฆฌ(Cleanup) ์ ๊ณต:
useEffect๊ฐ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๋ฑ๋กํ๊ฑฐ๋, ๊ตฌ๋ ์ ์ค์ ํ๊ฑฐ๋, ํ์ด๋จธ๋ฅผ ์์ํ๊ฑฐ๋, ๋ค๋ฅธ ์ธ๋ถ ๋ฆฌ์์ค๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ, ํด๋น ์์ ์ ๋๋๋ฆฌ๋ ์ ๋ฆฌ ํจ์๋ฅผ ๋ฐ๋์ ๋ฐํํด์ผ ํฉ๋๋ค. -
Effect๋ฅผ ์ง์ค์ ์ผ๋ก ์ ์ง: ๊ฐ
useEffectํ ์ ์ด์์ ์ผ๋ก ๋จ์ผํ๊ณ ์์ง๋ ฅ ์๋ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๊ด๋ฆฌํด์ผ ํฉ๋๋ค. ์ด๋ ์ดํํธ๋ฅผ ์ฝ๊ณ , ๋๋ฒ๊น ํ๊ณ , ๊ทธ ์ ๋ฆฌ ๋ก์ง์ ํฌํจํ์ฌ ์ถ๋ก ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค. -
์์กด์ฑ ๋ฐฐ์ด์ ์ฃผ์: ์์กด์ฑ ๋ฐฐ์ด์ ์ ํํ๊ฒ ์ ์ํ์ธ์. ๋ง์ดํธ/์ธ๋ง์ดํธ ์ดํํธ์๋ `[]`๋ฅผ ์ฌ์ฉํ๊ณ , ์ดํํธ๊ฐ ์์กดํ๋ ์ปดํฌ๋ํธ ๋ฒ์์ ๋ชจ๋ ๊ฐ(props, state, ํจ์)์ ํฌํจํ์ธ์.
useCallback๊ณผuseMemo๋ฅผ ํ์ฉํ์ฌ ํจ์ ๋ฐ ๊ฐ์ฒด ์์กด์ฑ์ ์์ ํ์์ผ ๋ถํ์ํ ์ดํํธ ์ฌ์คํ์ ๋ฐฉ์งํ์ธ์. -
๊ฐ๋ณ ๊ฐ์
useRefํ์ฉ: ์ดํํธ๋ ๊ทธ ์ ๋ฆฌ ํจ์๊ฐ *์ต์ * ๊ฐ๋ณ ๊ฐ(์ํ๋ props ๋ฑ)์ ์ ๊ทผํด์ผ ํ์ง๋ง ํด๋น ๊ฐ์ด ์ดํํธ์ ์ฌ์คํ์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ๋ฅผ ์ํ์ง ์์ ๋, ๊ทธ ๊ฐ์useRef์ ์ ์ฅํ์ธ์. ํด๋น ๊ฐ์ ์์กด์ฑ์ผ๋ก ํ๋ ๋ณ๋์useEffect์์ ref๋ฅผ ์ ๋ฐ์ดํธํ์ธ์. - ๋ณต์กํ ๋ก์ง ์ถ์ํ: ์ดํํธ(๋๋ ๊ด๋ จ ์ดํํธ ๊ทธ๋ฃน)๊ฐ ๋ณต์กํด์ง๊ฑฐ๋ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ, ์ด๋ฅผ ์ปค์คํ ํ ์ผ๋ก ์ถ์ถํ์ธ์. ์ด๋ ์ฝ๋ ๊ตฌ์ฑ, ์ฌ์ฌ์ฉ์ฑ ๋ฐ ํ ์คํธ ์ฉ์ด์ฑ์ ํฅ์์ํต๋๋ค.
- ์ ๋ฆฌ(Cleanup) ๋ก์ง ํ ์คํธ: ์ปค์คํ ํ ์ ์ ๋ฆฌ ๋ก์ง ํ ์คํธ๋ฅผ ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ํตํฉํ์ธ์. ์ปดํฌ๋ํธ๊ฐ ์ธ๋ง์ดํธ๋๊ฑฐ๋ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๋ ๋ฆฌ์์ค๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ํ ๋น ํด์ ๋๋์ง ํ์ธํ์ธ์.
-
์๋ฒ-์ฌ์ด๋ ๋ ๋๋ง(SSR) ๊ณ ๋ ค:
useEffect์ ๊ทธ ์ ๋ฆฌ ํจ์๋ SSR ์ค ์๋ฒ์์ ์คํ๋์ง ์๋๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์. ์ด๊ธฐ ์๋ฒ ๋ ๋๋ง ์ค์ ๋ธ๋ผ์ฐ์ ํน์ API(window๋document๋ฑ)๊ฐ ์๋ ์ํฉ์ ์ฝ๋๊ฐ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ๋๋ก ํ์ธ์. - ๊ฒฌ๊ณ ํ ์ค๋ฅ ์ฒ๋ฆฌ ๊ตฌํ: ์ดํํธ ๋ด์์ ๋ฐ์ํ ์ ์๋ ์ ์ฌ์ ์ค๋ฅ๋ฅผ ์์ํ๊ณ ์ฒ๋ฆฌํ์ธ์. ์ํ๋ฅผ ์ฌ์ฉํ์ฌ UI์ ์ค๋ฅ๋ฅผ ์ ๋ฌํ๊ณ ์ง๋จ์ ์ํด ๋ก๊น ์๋น์ค๋ฅผ ์ฌ์ฉํ์ธ์. ๋คํธ์ํฌ ์์ ์ ๊ฒฝ์ฐ, ๋ณต์๋ ฅ์ ์ํด ์ฌ์๋ ๋ฉ์ปค๋์ฆ์ ๊ณ ๋ คํ์ธ์.
๊ฒฐ๋ก : ์ฑ ์๊ฐ ์๋ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๋ก React ์ ํ๋ฆฌ์ผ์ด์ ๊ฐํํ๊ธฐ
React ์ปค์คํ ํ ์ ๋ถ์ง๋ฐํ ์ดํํธ ์ ๋ฆฌ์ ๊ฒฐํฉํ์ฌ ๊ณ ํ์ง ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ๋ฐ ์์ด์๋ ์ ๋ ๋๊ตฌ์ ๋๋ค. ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ ๊ธฐ์ ์ ๋ง์คํฐํจ์ผ๋ก์จ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ณ , ์์์น ๋ชปํ ๋์์ ์ ๊ฑฐํ๋ฉฐ, ์ฑ๋ฅ์ ์ต์ ํํ๊ณ , ์ฌ์ฉ์์ ์์น, ์ฅ์น ๋๋ ๋คํธ์ํฌ ์กฐ๊ฑด์ ๊ด๊ณ์์ด ๋ ์ ๋ขฐํ ์ ์๊ณ ์ผ๊ด๋ ๊ฒฝํ์ ๋ง๋ค์ด๋ ๋๋ค.
useEffect์ ํ๊ณผ ํจ๊ป ์ค๋ ์ฑ
์์ ๋ฐ์๋ค์ด์ธ์. ์ ๋ฆฌ๋ฅผ ์ผ๋์ ๋๊ณ ์ปค์คํ
ํ
์ ์ ์คํ๊ฒ ์ค๊ณํจ์ผ๋ก์จ, ๋น์ ์ ๋จ์ง ๊ธฐ๋ฅ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ, ์๊ฐ๊ณผ ๊ท๋ชจ์ ์ํ์ ๊ฒฌ๋๊ณ ๋ค์ํ๊ณ ๊ธ๋ก๋ฒํ ์ฌ์ฉ์์๊ฒ ์๋น์ค๋ฅผ ์ ๊ณตํ ์ค๋น๊ฐ ๋, ๋ณต์๋ ฅ ์๊ณ ํจ์จ์ ์ด๋ฉฐ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ์ํํธ์จ์ด๋ฅผ ๋ง๋๋ ๊ฒ์
๋๋ค. ์ด๋ฌํ ์์น์ ๋ํ ๋น์ ์ ํ์ ์ ์์ฌํ ์ฌ์ง์์ด ๋ ๊ฑด๊ฐํ ์ฝ๋๋ฒ ์ด์ค์ ๋ ํ๋ณตํ ์ฌ์ฉ์๋ก ์ด์ด์ง ๊ฒ์
๋๋ค.