๐จ 09. ๋ ๋ ์ต์ ํ์ ๋ฌ์ฝคํ ๊ฑฐ์ง๋ง (useMemo, useCallback)
๐ ๊ฐ์
useMemo์ useCallback์ ๋ฌด๋ถ๋ณํ๊ฒ ๋จ๋ฐํ ๋ ๋ฐ์ํ๋ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋์, ์ง์ ํ ์ต์ ํ์ ํ์ด๋ฐ์ ๋ฐฐ์ฐ๋ ๊ฐ์ด๋์ ๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ต์ ํ ํ (
useMemo,useCallback)์ด ๊ณต์ง๊ฐ ์๋๋ฉฐ ์คํ๋ ค ์ปดํฌ๋ํธ๋ฅผ ๋ณ๋ค๊ฒ ํ๋ ์๋ฆฌ๋ฅผ ์ค๋ช ํ ์ ์๋ค.React.memo์ ๊ฒฐํฉ๋์ง ์์useCallback์ด ์ผ๋ง๋ ๋ฌด์๋ฏธํ ์ง์ธ์ง ๊นจ๋ซ๊ฒ ๋๋ค.- "์ฃ๋ถ๋ฅธ ์ต์ ํ๋ ๋ง์ ์ ๊ทผ์"์ด๋ผ๋ ์์นํ์, ์ธ์ ์ง์ง ์ด ํ ๋ค์ ๊บผ๋ด๋ค์ด์ผ ํ ์ง ๋ช ํํ ๊ธฐ์ค์ ํ๋ฆฝํ๋ค.
๐ ๋ชฉ์ฐจ
- ๐ค ์ ์์์ผ ํ๋๊ฐ: '์ต์ ํ'๋ผ๋ ๋ฌดํ์ ๊ณตํฌ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ ๊ทธ๋ผ ์ง์ง๋ ์ด๋์ ์ฐ๋๊ฐ? (์ ํํ ์ฌ์ฉ ํ์ด๋ฐ)
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ / ํต์ฌ ํํธ: 10๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์ฒ (์ ์
):"์ ๋ฐฐ๋! ์ ๊ฐ ์ฐ๋ฆฌ ์ปค๋ฎค๋ํฐ ์ฑ์ ์์ฒญ๋๊ฒ ์ต์ ํํ์ด์. ๋ชจ๋ ํจ์๋
useCallback์ผ๋ก ๊ฐ์ธ๊ณ , ๋ชจ๋ ๊ฐ์ฒด์ ๋ฐฐ์ด์useMemo๋ก ๊ฐ์์ต๋๋ค! ์ด์ ์์ฒญ ๋น ๋ฅด๊ฒ ์ฃ ?" - ์ํธ(๋ฆฌ๋): "์์ฒ ๋... ์ง๊ธ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ ๋ฐฐ, ์ธ ๋ฐฐ๋ก ๊น์๋จน์ผ๋ฉด์ '์ต์ ํ'๋ผ๊ณ ๋ถ๋ฅด์๋ ๊ฑด๊ฐ์? ๋ธ๋ผ์ฐ์ ๋ฉ๋ชจ๋ฆฌ ํญ์ด ํฐ์ง๊ธฐ ์ง์ ์ ๋๋ค."
๐ค ์ ์์์ผ ํ๋๊ฐ: '์ต์ ํ'๋ผ๋ ๋ฌดํ์ ๊ณตํฌ
ํ๋ก ํธ์๋ ์ค๋ฌด ๊ธฐ์ ๋ฉด์ ์์ ์ฃผ๋์ด๋ค์ด ๊ฐ์ฅ ๋ง์ด ํ๋ฝํ๋ ์ง๋ฌธ์ด ์์ต๋๋ค. "์ด๋ ์ํฉ์์ useMemo์ useCallback์ ์ฐ๋์?"
๋๋ถ๋ถ์ "๋ ๋๋ง ์ฑ๋ฅ์ ๋์ด๊ธฐ ์ํด์์", "๋งค๋ฒ ํจ์ ์๋ก ๋ง๋ค์ด์ง๋ ๊ฑธ ๋ง์ผ๋ ค๊ณ ์" ๋ผ๊ณ ๋ต๋ณํฉ๋๋ค. ์ด ๋๋ต์ ๋ฐ์ ๋ง๊ณ ๋ฐ์ ํ๋ ธ์ต๋๋ค (์ฌ์ค ๊ฑฐ์ ํ๋ ธ์ต๋๋ค).
๐ค ์ ๊น, ๋จผ์ ์๊ฐํด๋ด
๋ง์ ์ ํ ๋ฒ ํ๋ ํจ์๊ฐ ์์ด. ์ด๊ฑธuseCallback์ผ๋ก ๊ฐ์ธ๋ ๊ฒ ๊ทธ๋ฅ ์์ง๋ก ๋ ๋๋ ๊ฒ๋ณด๋ค ๋ ๋น ๋ฅผ๊น?
// โ ์์ฒ ์ด์ ๋ฌด์ง์ฑ ์ต์ ํ (Premature Optimization)
function Counter() {
const [count, setCount] = useState(0);
// ์์ฒ : "์! ํจ์๊ฐ ์๊พธ ๋ถํ์ํ๊ฒ ์์ฑ๋๋ ๊ฑธ ๋ง์์ผ ํด!"
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <button onClick={handleClick}>ํด๋ฆญ {count}</button>;
}์์ฒ ์ด๋ ์ด ์ฝ๋๋ฅผ ์ง๊ณ ์ค์ค๋ก๋ฅผ ์ต์ ํ ๋ง์คํฐ๋ผ ๋ฟ๋ฏํดํ๊ฒ ์ง๋ง, ๋ฆฌ์กํธ ์ฐฝ์์๋ค์ ์ด ์ฝ๋๋ฅผ ๋ณด๋ฉด ํ๋ฅผ ๋์ฏง ์ฐน๋๋ค. ์ด ์ฝ๋๋ ํ ์ ์ ์ด ์ฝ๋๋ณด๋ค ํจ์ฌ, ๋ฌด์กฐ๊ฑด ๋ ๋๋ฆฌ๊ณ ๋์ฐํ ์ฝ๋์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
์ผ๋ฐ ํจ์ ์ ์ํ๊ธฐ: ์๋ง๊ฐ ๋ฉ๋ชจ์ง์ "์ฌ๊ณผ ์ฌ ์ค๊ธฐ"๋ผ๊ณ ๊ฐ๋ณ๊ฒ ํ๊ฐ๊ฒจ ์จ์ ์ง๊ฐ์ ๋ฃ์ด๋๋ ๊ฒ. (๋ฉ๋ชจ์ง ํ ์ฅ์ง๋ฆฌ, 0.0001์ด๋ฉด ์)
useCallback ์ฐ๊ธฐ: "์ฌ๊ณผ ์ฌ ์ค๊ธฐ"๋ผ๋ ๋ฉ๋ชจ์ง๋ฅผ ๊ณต์ฆ ์ฌ๋ฌด์(React ์์ง ๋ด์ฅ ๋ฉ๋ชจ๋ฆฌ ์บ์ ํ์ ์ฒด๊ณ)์ ๊ฐ์ ธ๊ฐ์, ๋์ฅ์ ์พ ์ฐ๊ณ , ๋๊บผ์ด ์บ๋น๋(๋ฐฐ์ด)์ ์ด์ด ์ ๋ฒ์ ๋ฃ์ด๋ ๋ฉ๋ชจ์ง๋ ๋ด์ฉ์ด ๋๊ฐ์์ง ํ๋ํ๋ ๋์กฐํด ๋ณธ ๋ค, ๋๊ฐ์ผ๋ฉด ์๋ ๋ฉ๋ชจ์ง๋ฅผ ๊บผ๋ด์ฃผ๋ ํ์.๊ณ ์ "์ฌ๊ณผ ํ๋ ์ฌ ์"๋ผ๋ 1์ด์ง๋ฆฌ ๊ณ์ฐ์์ ์ํด ์ ๊ฑฐ์ฐฝํ ๊ณต์ฆ ์ฌ๋ฌด์๋ฅผ ๋ฐฉ๋ฌธํ๋ ๊ฒ ๋ง์๊น? ์๋์ง. ๋ฐฐ๋ณด๋ค ๋ฐฐ๊ผฝ์ด ํจ์ฌ ์ปค์ง์์. ๊ทธ๋ฅ 1์ด ๊ฑธ๋ ค์ ๋ฉ๋ชจ์ง ์ฟจํ๊ฒ ์ฐข๊ณ ์๋ก ํ๊ฐ๊ฒจ ์ฐ๋ ๊ฒ ๋ฐฑ๋ง ๋ฐฐ๋ ๋ ์ด๋์ด์ผ!
โ
ํต์ฌ ์๋ฆฌ:
useMemo์ useCallback์ ๊ณต์ง ๋ง๋ฒ์ด ์๋๋๋ค. ํด๋น ํ
์ ์คํํ๊ธฐ ์ํด ๋ฆฌ์กํธ๋ ์์กด์ฑ ๋ฐฐ์ด([])์ ๋ค์ด๊ฐ ๊ฐ๋ค์ ๋ฃจํ๋ฅผ ๋๋ฉฐ ์ด์ ๊ฐ๊ณผ ์ผ์ผ์ด ๋น๊ต(Object.is)ํด์ผ ํ๊ณ , ํ ๋ฉ๋ชจ๋ฆฌ์ ํน์ํ ์บ์ ๊ณต๊ฐ์ ๋ณ๋๋ก ์ ์ ํด์ผ ํฉ๋๋ค.
์ฆ, "ํ
์ ์คํํ๊ณ ๊ณ์ฐ์ ์บ์ฑํ๋ ๋น์ฉ(Overhead)"์ด "๋งค ๋ ๋๋ง๋ง๋ค ๋จ์ํ ์๋ฐ์คํฌ๋ฆฝํธ ํจ์/๊ฐ์ฒด๋ฅผ ํ๋ ์๋ก ๋ถ์ด๋นต ์ฐ์ด๋ด๊ณ ๊ฐ๋น์ง ์ปฌ๋ ํฐ(GC)์ ๋ฒ๋ฆฌ๋ ๋น์ฉ"๋ณด๋ค ์๋์ ์ผ๋ก ๋น์๋๋ค. ์๋ฐ์คํฌ๋ฆฝํธ์ V8 ์์ง์ ํจ์ ํ ๋น๊ณผ ์ฐ๋ ๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์์์ ์ด์ํ ์ ๋๋ก ๋ฏธ์น ๋ฏ์ด ์ํฉ๋๋ค.
๐งฉ ๊ทธ๋ผ ์ง์ง๋ ์ด๋์ ์ฐ๋๊ฐ? (์ ํํ ์ฌ์ฉ ํ์ด๋ฐ)
ํ์ด๋ฐ 1. [์ง์ง ๋ฌด๊ฑฐ์ด] ์ฐ์ฐ์ ์บ์ฑ (useMemo)
100์์ง๋ฆฌ ๋ง์
(๋ฒํผ ํด๋ฆญ ํจ์ ๋ฑ)์ ๊ทธ๋ฅ ๋ ๋๋ง๋ง๋ค ์๋ก ๋ฒ๋ฆฌ๊ณ ๋ง๋์ธ์.
ํ์ง๋ง "์ํ ๋ฐ์ดํฐ 1๋ง ๊ฐ ์ ๋ ฌ", "3D WebGL ํ์ฑ ๋ฐ์ดํฐ ๊ณ์ฐ" ๊ฐ์ ๊ฒ๋ค์ ๋ฌด์กฐ๊ฑด ์บ์ฑํด์ผ ํฉ๋๋ค.
// โ
์ฌ๋ฐ๋ฅธ ํ์ด๋ฐ: CPU๊ฐ ๋น๋ช
์ ์ง๋ฅด๋ ์ฐ์ฐ (Pro Approach)
function AnalyticsDashboard({ userData }) {
const [tab, setTab] = useState('chart');
// userData๊ฐ ๋ฐ๋ ๋๋ง ์ด ์ง์ฅ์ 1์ด์ง๋ฆฌ ๋ฃจํ ์ฐ์ฐ์ ์ํํ๊ณ ,
// ํญ(tab)์ด ๋ฐ๋ ๋๋ ์ด ์ฐ์ฐ์ ์ ํผํด์ ์บ์๋ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ์ฌํ์ฉํ๋ค!
const heavyProcessedData = useMemo(() => {
return runHeavyMathAlgorithm(userData); // ์คํ์ 500ms ๊ฑธ๋ฆฌ๋ ๋ฏธ์น ํจ์
}, [userData]);
return <div>...</div>;
}ํ์ด๋ฐ 2. ์์ ์ปดํฌ๋ํธ์ ๋ ๋๋ง ํญํฌ์ ๋ฐฉ์ด (useCallback + React.memo)
๊ฐ์ฅ ํํ๊ฒ useCallback์ ์ค๋จ์ฉํ๋ ์ผ์ด์ค๊ฐ, "๊ทธ๋ฅ ์์ ์์ํํ
์ธ๋ผ์ธ ํจ์ ์ ๋๊ธฐ๋ ค๊ณ ์"์
๋๋ค.
onClick={handlePlay}๋ก ํจ์๋ฅผ ๊ทธ๋ฅ ๋๊ธฐ๋ useCallback์ผ๋ก ๊ฐ์ธ์ ๋๊ธฐ๋ , ์์ ์ปดํฌ๋ํธ์ธ <Button />์ ๋ถ๋ชจ ๋ ๋๋ง ์ ๋ฌด.์กฐ.๊ฑด ๋๊ฐ์ด ๋ฆฌ๋ ๋๋ง๋ฉ๋๋ค!
useCallback์ด ์ง์ง ํ์ ๋ฐํํ๋ ค๋ฉด, ๋ฐ๋ ์์ ์ปดํฌ๋ํธ๊ฐ React.memo๋ผ๋ ์ฒ ๊ฐ์ ๋๋ฅด๊ณ ์์ด์ผ ๋น๋ก์ ์ ํจํ๊ฐ ๋ค์ด๊ฐ๋๋ค.
// ๐ฏ ์์ ์ปดํฌ๋ํธ์ ์ฒ ๊ฐ(React.memo)์ ๋๋ ๋ค!
// props๊ฐ ๋๊ฐ๋ค๋ฉด ๋ด ๋ ๋๋ง์ ์ ๋ ์ ํ ๊ฑฐ์ผ!
const HeavyChart = React.memo(({ onZoom }) => {
return <div>์ด๊ณ ํด์๋ ๋ฌด๊ฑฐ์ด ์ฐจํธ</div>;
});
function Dashboard() {
const [text, setText] = useState('');
// โ
์๋ฒฝํ ์ต์ ํ:
// 1. useCallback์ผ๋ก onZoom ํจ์์ ๊ป๋ฐ๊ธฐ(์ฃผ์๊ฐ)๋ฅผ ์ผ๋ฆฐ๋ค.
// 2. HeavyChart๋ React.memo ๋๋ถ์ onZoom ์ฃผ์๊ฐ์ด ๊ทธ๋๋ก์ด๋ฏ๋ก ๋ ๋๋ง์ ๋ฉ์ถ๋ค!
// ๊ฒฐ๊ณผ: ํ
์คํธ ๋ฐ์ค์ ๊ธ์ ์น ๋๋ง๋ค ์ด ๋ฌด๊ฑฐ์ด ์ฐจํธ๊ฐ ๋ ๋๋ง๋๋ ๋์ฌ์์ ์๋ฒฝํ ๋ฐฉ์ด!
const handleZoom = useCallback((level) => {
console.log("์ค ์๋!", level);
}, []);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<HeavyChart onZoom={handleZoom} />
</>
);
}ํ์ด๋ฐ 3. ๋จ์ Effect์ ์์กด์ฑ ๋ณ์๋ก ๋์ ธ์ง ๋
์ด๊ฑด ์ด์ ๊ฐ์ด๋ 06์์ ๋ฐฐ์ ์ง? ๊ฐ์ฒด๋ ํจ์๋ฅผ ๋ง๋ค์ด์ ์์ ์ปดํฌ๋ํธ์ useEffect ์์กด์ฑ ๋ฐฐ์ด([ ])๋ก ๋์ ธ๋ฒ๋ฆฌ๋ฉด ๋ฌดํ ๋ฃจํ ํญํ์ด ํฐ์ง ์ ์์ด. ์ด๋ ๊ทธ๊ฑธ ๋ฐฉ์ดํ๊ธฐ ์ํ ๋ฐฉํจ๋ง์ด์ฉ์ผ๋ก useMemo/useCallback์ ์ฌ์ฉํด ์ฐธ์กฐ๋ฅผ ์ผ๋ ค์ฃผ๋ ๊ฑฐ์ง.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
โ ๊ฐ์ฒด์ ๋ฐฐ์ด์ React.memo ์์์๊ฒ ์์ง๋ก ๋๊ธธ ๋
์ํฉ: ์์ฒ ์ด๊ฐ ์ฐจํธ ์ปดํฌ๋ํธ๋ฅผ React.memo๋ก ๊ธฐ๊ป ๊ฐ์ธ๋จ์ด. ๊ทธ๋ฐ๋ฐ๋ ์ฐจํธ๊ฐ ๊ณ์ ๋ฆฌ๋ ๋๋ง์ด ๋๋!
const Chart = React.memo(({ data, options }) => { ... });
function Dashboard() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c+1)}>์
๋</button>
{/* ๐จ ๋ฒํผ์ ๋๋ฅด๋ฉด Chart๊ฐ ์ฌ์ ํ ๋ฏธ์น๋ฏ์ด ๋ฆฌ๋ ๋๋ง ๋จ! */}
<Chart
data={[1, 2, 3]}
options={{ color: 'red' }}
/>
</div>
);
}์์ธ: React.memo๋ props๋ฅผ ์์ ๋น๊ต(Object.is)ํด.
๋ฒํผ์ ๋๋ฌ์ ๋ ๋๋ง ์ค๋
์ท์ด ๊ฐฑ์ ๋ ๋๋ง๋ค, ๋ถ๋ชจ ํธ๋ฆฌ์ JSX ์ฝ๋ ์์ ์ ํ ์๋ [1, 2, 3]์ด๋ผ๋ ๋์ ๋ฉ๋ชจ๋ฆฌ(Heap)์ ์์ ํ ์๋ก์ด ์ฃผ์๋ฅผ ๊ฐ์ง ๋ฐฐ์ด๋ก ์ฌํ์ํด! options ๊ฐ์ฒด๋ ๋ง์ฐฌ๊ฐ์ง์ผ.
React.memo ๋ด: "์! data(๋ฐฐ์ด ๊ป๋ฐ๊ธฐ) ์ฃผ์๊ฐ ๋ฐ๋์๋ค! ๋ค์ ๊ทธ๋ ค!" ๋ฐฉ์ด๋ง์ด ์ข
์์ฅ์ฒ๋ผ ๋ฌด๋์ง ๊ฑฐ์ผ.
ํด๊ฒฐ์ฑ
:
๊ฐ์ฒด ๋ฐฐ์ด์ ์๊ตฌ ๋๊ฒฐ์์ผ ๋๊ฒจ๋ผ!
// โ
๋ฐ์ผ๋ก ๋นผ๋ด๊ฑฐ๋(์ ์ ๋ฐ์ดํฐ)
const CHART_DATA = [1, 2, 3];
// โ
๋ด๋ถ์์ ์จ์ผ ํ๋ค๋ฉด useMemo๋ก ์ผ๋ฆฌ๊ธฐ
const chartOptions = useMemo(() => ({ color: 'red' }), []);
return <Chart data={CHART_DATA} options={chartOptions} />;๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ์ต์ ํ์ ๋์ | โ ํ๋ฆฐ ์ด์ | โ ์ง์ง ํ๊ฒฉํ์ ๊ฐ์ด๋ |
|---|---|---|
๋ก๊น ๋ฒํผ๋ค์ onClick ํจ์๋ค | useCallback ๋ก์น (์บ์ฑ ์ค๋ฒํค๋๊ฐ ๋ ํผ, ์ฐ๋ ๊ธฐ GC๊ฐ ๋ ๋น ๋ฆ) | ๊ทธ๋ฅ ํ๋ฒํ๊ฒ ํ์ดํ ํจ์ ๋์ง๊ธฐ (() => setCount(c+1)) |
| API ์๋ต 10๊ฐ ์ฒ๋ฆฌ ๋ฑ ์ผ๋ฐ ๋ก์ง | useMemo ๋ก์น (๋ฉ๋ชจ๋ฆฌ๋ง ์์ฒญ ๋จน์) | ๊ทธ๋ฅ ๋ ๋๋ง ๋์ค const result = ...๋ก ๋ฐ๋ก ์ฒ๋ฆฌ |
| ์์ ์์๋ก ๋ด๋ ค๋ณด๋ด๋ ๊ฐ์ฒด/ํจ์ | ์์ ์ปดํฌ๋ํธ๊ฐ ์ผ๋ฐ ํจ์ํ์ (React.memo ์์ผ๋ฉด useCallback ์จ๋ดค์ ๋ฆฌ๋ ๋๋ง ๋จ) | ๋ฐ๋์ ์์์ React.memo๋ก ๊ฐ์ผ ํ, ๊ทธ ํ๋กญ์ค๋ฅผ useCallback/useMemo๋ก ์ผ๋ ค์ ํ์ฌํ๊ธฐ! |
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
"์ฃ๋ถ๋ฅธ ์ต์ ํ๋ ๋ง์ ์ ๊ทผ์์ด๋ค." (๋๋๋ ํฌ๋์ค)
useMemo์useCallback์ ๋ด ์ปดํฌ๋ํธ๊ฐ ๋ฒ๋ฒ ๊ฑฐ๋ ค์ ๋์ ํ ๋ชป ๋ด์ฃผ๊ฒ ์ ๋, ํ๋กํ์ผ๋ฌ(Profiler)๋ฅผ ์ผ์ ๋ ๋๋ง ๋ฒ์ธ์ ์ฐพ์๋ธ ๋ค์๋ง ํ๊ฐ๋๋ ๋ณ๊ธฐ(Weapon)๋ค. ๋ฐฉ์ด๊ตฌ(๊ธฐ๋ณธ ์ฅ๋น)๊ฐ ์ ๋ ์๋๋ค.
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
๋ด๊ฐ ์จ ์ฌ๋ฐฉ์ ๋ฐ๋ผ๋ useCallback์ด ์ฌ์ค์ ์ฑ์ ๋ ๋๋ฆฌ๊ฒ ๋ง๋ค๊ณ ์์๋ค๋... ํจ์ ์๋ก ๋ง๋๋ ๊ฒ ๊ณต์ฆ ์ฌ๋ฌด์ ๊ฐ๋ ๊ฒ๋ณด๋ค ๋ฐฑ๋ง ๋ฐฐ ์ด๋์ด๋ผ๋ ๋น์ ๊ฐ ๋ผ๋ฅผ ๋๋ ธ๋ค.
๐ก "ํ (Hook)์ ๊ณต์ง๊ฐ ์๋๋ค! ์ ๋ง ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ์บ์ฑํ๊ฑฐ๋,
React.memo๋ฅผ ๋๋ฅธ ์์์๊ฒ ๊ป๋ฐ๊ธฐ๋ฅผ ๋ณด์กดํด์ ๋๊ธธ ๋๋ง ๊บผ๋ด๋๋ ๋น์ฅ์ ๋ฌด๊ธฐ๋ค."
์ต์ ํ๋์๊ณ ๋ก์น ํ๋ ์ฝ๋๋ค์ด ์ฌ์ค์ ๋ฉ๋ชจ๋ฆฌ์ ๋ฌด๋ค์ด์๋ค๋ ์ผ๊ตด์ด ํ๋๊ฑฐ๋ฆฐ๋ค. ๊ทธ๋๋ ์ํธ๋ ๋์ "์ง์ง ํ๊ฒฉ ํ์ "์ ๋ฐฐ์ ์ผ๋๊น ์๊น์ง ์๋ค. ๋ด์ผ ๋น์ฅ ์ฐ๋ ๊ธฐ ํ ๋ค ์ฃ๋ค ์ฐข์ด์ ๋ฒ๋ ค๋ฒ๋ฆฌ๊ณ ๋ฐ๋๋ผ์ ์คํผ๋๋ฅผ ๋๊ปด๋ด์ผ๊ฒ ๋ค.
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์์ฒ ์ด๊ฐ ๋ค์์ฒ๋ผ useCallback์ ๋จ๋ฐํ์ต๋๋ค. ์ด ์ฝ๋๊ฐ ์์ง๋ก ํจ์๋ฅผ ๋ง๋๋ ์ฝ๋๋ณด๋ค ์ฑ๋ฅ์ด ์คํ๋ ค '์ ์ข์(์
ํ๋)' ์ด์ ๋ฅผ ๊ฐ์ฅ ์ ํํ ์ค๋ช
ํ ๊ฒ์?
function SimpleCard() {
const [like, setLike] = useState(false);
const toggleLike = useCallback(() => {
setLike(!like);
}, [like]);
return <button onClick={toggleLike}>์ข์์</button>;
}- A)
useCallback์ ๋ธ๋ผ์ฐ์ ์ ์ ์ญ GPU ๊ฐ์์ ์ธ๋ฐ์์ด ํธ์ถํ๊ธฐ ๋๋ฌธ. - B) ์ด ํจ์๋
[like]์์กด์ฑ์ ๊ฐ์ง๊ณ ์์ผ๋ฏ๋ก ์ ์ ๊ฐ ๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค(์ํ๊ฐ ๋ณํ๋ฏ๋ก) ์ด์ฐจํผ ๊ณ์ "ํจ์๋ฅผ ์๋ก ์์ฑ"ํด์ผ ํ๋ค. ์ด๋ ๊ธฐ์กด ํจ์ ์์ฑ์ ํ๊ดดํ๋ ํผ(๊ธฐ๋ณธ ๋์)์ ๋ง๋ถ์ฌ ์์กด์ฑ ๋ฐฐ์ด์ ๋ฃจํ ๋๋ฉฐ ๋น๊ตํ๋ ์ค๋ฒํค๋ ์ฐ์ฐ๊น์ง ๋ค์ผ๋ก ๋ ์์๊ธฐ ๋๋ฌธ. - C)
useCallback์ด ๋ถ๋ฆฌ๋ ์๊ฐ ์์ ์ปดํฌ๋ํธ๋ค์ ๋ชจ๋ ๋ฆฌ๋ ๋๋ง ํ์ดํ๋ผ์ธ์ด ์ ์ง๋๊ธฐ ๋๋ฌธ. - D)
useCallback์์์๋ ๋ถ๋ณ์ฑ ์๋ฐฐ(!like)๊ฐ ๊ฐ์ง๋์ด ์๋ฌ๋ฅผ ๋ฑ๊ธฐ ๋๋ฌธ.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: ์ต์ ํ์ ์ญ์ค์ ์ฐ๋ฅด๋ ๋ง์คํฐํผ์ค ์ค๊ณ ๋ฌธ์ ์
๋๋ค! ์์ฒ ์ด๋ ํจ์ ์์ฑ์ ๋ง๊ฒ ๋ต์๊ณ ์ ๊ฑธ ์ผ์ง๋ง, ๋ฒํผ์ ํด๋ฆญํ ๋๋ง๋ค like ๊ฐ์ด ๋ฐ๋๋๋ค. ์์กด์ฑ ๋ฐฐ์ด [like]๊ฐ ๋ฐ๋์์ผ๋ฏ๋ก ๋ฆฌ์กํธ๋ ์ด์ฐจํผ ๊ธฐ์กด ์บ์๋ฅผ ๋ฒ๋ฆฌ๊ณ ํจ์๋ฅผ ๋ ์๋ก ๋ง๋ญ๋๋ค(์บ์ฑ ์คํจ).
๊ฒฐ๊ตญ ์ด ์ง์ 1. ํจ์ ์๋ก ๋ง๋ค๊ธฐ + 2. ์ด์ like์ ์ง๊ธ like ๋น๊ตํ๋ ์ถ๊ฐ ์ฐ์ฐํ๊ธฐ ๋ผ๋ ๋๋ธ ํ๋ํฐ๋ฅผ ๋ฌผ๊ฒ ๋ฉ๋๋ค. ์๋ฌด ์ด๋ ์์ด ๋ฉ๋ชจ๋ฆฌ์ CPU ์๊ฐ๋ง ํ๋จน๋ ์ต์
์ ํ์์
๋๋ค.
Q2. useMemo๋ฅผ ๋ฐ๋์, ๋ฌด์กฐ๊ฑด์ ์ผ๋ก ๋์
ํด์ผ ํ๋ ์ ์ผํ(๊ฐ์ฅ ํ์คํ) ํ์ด๋ฐ์ ๊ณจ๋ผ๋ณด์ธ์.
- A) ๋ฐฐ์ด์
map๋ฉ์๋๋ก UI ๋ฆฌ์คํธ(<li/>)๋ฅผ 10์ฌ ๊ฐ ์ํํด์ ๊ทธ๋ ค๋ผ ๋. - B) ๋ฐฑ์๋์์ ๋ฐ์์จ ๊ฑฐ๋ํ 10,000์ค์ JSON ๋ฐฐ์ด์ ์ฌ๊ฐ๊ณตํ๊ณ ํด๋ผ์ด์ธํธ๋จ์์ ์ ๋ ฌ(Sort) ์๊ณ ๋ฆฌ์ฆ์ ํ์ธ ๋.
- C)
<input>์ฐฝ์์ ์ ์ ๊ฐ ํ์ดํํ ๋๋ง๋ค ๊ทธ state ๊ฐ์ ์ถ์ ํด์ผ ํ ๋. - D) ์ธ๋ถ์ React Router ๊ฒฝ๋ก(
useLocation)์ ๋ณ๊ฒฝ์ ๊ฐ์งํด์ผ ํ ๋.
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค: B์ฒ๋ผ ๋ช
๋ฐฑํ๊ฒ ๊ณ ๋น์ฉ ๋ณ๋ชฉ ์ฐ์ฐ("Expensive Calculation")์ ์ฒ๋ฆฌํ ๋๊ฐ ๋ฐ๋ก useMemo์ ์ง์ ํ ๋ฌด๋์
๋๋ค. A์ ์์ํ ๊ทธ๋ฆฌ๊ธฐ, C์ ๋จ์ ์ํ ์ถ์ ๋ฑ์ ๊ทธ๋ฅ ๋ ๋๋ง ๋จ๊ณ์์ ๋ ๊ฒ์ผ๋ก ๋์ ธ์ฃผ์ธ์. ์๋ฐ์คํฌ๋ฆฝํธ๋ ๋น์ ์ด ์๊ฐํ๋ ๊ฒ๋ณด๋ค ์๋ฐฑ ๋ฐฐ ๋น ๋ฆ
๋๋ค. ์ธ๋ฐ์์ด ๋ค useMemo ์ฒ๋ฆฌํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ ๋ฆญ(Leak)๋ง ๋ฐ์ํฉ๋๋ค.
Q3. ๋ค์์ '์ต์ ํ ์คํจ' ์ผ์ด์ค๋ฅผ ๋ณด๊ณ , React.memo์ useCallback์ ์ ์๋ก ๋ผ๋์ผ ๋ ์ ์๋ ์ธํธ ๋ฌด๊ธฐ์ธ์ง๋ฅผ ์ค๋ช
ํด๋ณด์ธ์.
// 1. ์์: React.memo() ์์ด ๋ ๊ฒ์ ์ปดํฌ๋ํธ
function ChildButton({ onClick }) {
console.log("์์ ๋ฆฌ๋ ๋๋ง ๋จ!");
return <button onClick={onClick}>๋๋ฅผ ๋๋ฌ์</button>;
}
// 2. ๋ถ๋ชจ:
function Parent() {
const [val, setVal] = useState('');
// ์์ฒ : "์ํํ! useCallback์ผ๋ก ํจ์ ์ฐธ์กฐ๊ฐ์ ์๋ฒฝํ ๋๊ฒฐ์์ผฐ์ผ๋,
// ์
๋ ฅ์ฐฝ์ ํ์ดํ(val ๊ฐฑ์ )ํด๋ ์์์ ์ ๋ ๋ฆฌ๋ ๋๋ง ์ ๋๊ฒ ์ง?"
const handleClick = useCallback(() => console.log('๋๋ ท๋ฐ'), []);
return (
<div>
<input value={val} onChange={e => setVal(e.target.value)} />
<ChildButton onClick={handleClick} />
</div>
);
}โ ์ ๋ต ๋ฐ ์ฃผ๊ด์ ํด์ค:
์์ฒ ์ด์ ์์๊ณผ ๋ฌ๋ฆฌ, ์ธํ ์ฐฝ์ ๊ธ์๋ฅผ ์น ๋๋ง๋ค ์ฝ์์๋ **"์์ ๋ฆฌ๋ ๋๋ง ๋จ!"**์ด ๋ฏธ์น ๋ฏ์ด ์ฐํ๋๋ค.
์ด์ (Why): ๋ฆฌ์กํธ์ ๊ธฐ๋ณธ ๋ ๋๋ง ๊ท์น 1์กฐ 1ํญ, **"๋ถ๋ชจ ์ปดํฌ๋ํธ(Parent)์ state๊ฐ ๋ฐ๋์ด ๋ ๋๋ง์ด ๋๋ฉด, ๊ทธ ์์(Child)๋ค์ ํ๋กญ์ค๊ฐ ๋ณํ๋ ์ ๋ณํ๋ ๋ฌป์ง๋ ๋ฐ์ง์ง๋ ์๊ณ ๋ฌด์กฐ๊ฑด ์น ๋ค ๊ฐ์ ๋ฆฌ๋ ๋๋ง๋๋ค"**๋ผ๋ ๋์์น ๋๋ฌธ์
๋๋ค.
๋ถ๋ชจ๊ฐ useCallback์ผ๋ก ๊ธฐ๊ป onClick์ ์ฃผ์๊ฐ(ํฌ์ฅ์ง)์ ๊ทธ๋๋ก ์ ์งํด์ ๋๊ฒจ์คฌ๋๋ผ๋, ์์์ธ <ChildButton>์ด ๊ทธ ํฌ์ฅ์ง๊ฐ ์์ ๊ณผ ๊ฐ์์ง ๋ค๋ฅธ์ง ๋๋ฑ์ฑ ๊ฒ์ฌ๋ฅผ ๋ง์๋ด๋ ๋ฐฉํจ(React.memo)๋ฅผ ์
๊ณ ์์ง ์๋ค๋ฉด, ๊ทธ๋ฅ ๋ฌด์ง์ฑ์ผ๋ก ์ธ๋ ค ๋ด๋ ค๊ฐ ์๋ก ๋ ๋๋ง์ ๋๋๋ค!
๋ฐ๋ผ์ ๋ถ๋ชจ์ useCallback์ ๋ฐ๋์ ์์์ React.memo๋ผ๋ ๋ฐฉ์ด๋ง ์์ ฏ๊ณผ ์์ผ๋ก ๊ฒฐํฉ๋์์ ๋๋ง ๋น๋ก์ '๋ ๋๋ง ์ต์ ํ ์ฝค๋ณด'๋ก์ ์๋ํฉ๋๋ค. ๋ฐ์ชฝ์ง๋ฆฌ ์ต์ ํ๋ ๊ฑฐ๋ํ ํจ์ ์ผ ๋ฟ์
๋๋ค.