๋ฆฌ์กํธ์์ ์ฑ๋ฅ์ ์ต์ ํ ํ ์ ์๋ ๋ฐฉ๋ฒ์๋ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์๋ค. ์ด๋ฒ์ ๊ทธ ์ค์์๋ ๋ฆฌ์กํธ ์ปดํฌ๋ํธ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ์ฌ(memoization) ๋ถํ์ํ ๋ฆฌ๋ ๋๋ฅผ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ๋ค.
Memoization์ด๋
๋ฉ๋ชจ์ด์ ์ด์ ์ ์ฐ์ฐ์ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์บ์์ ์ ์ฅํด๋๊ณ ๋์ผํ ์ฐ์ฐ์ด ๋ฐ๋ณต๋ ๋, ์ ๋ ฅ๊ฐ์ด ์ ๊ณผ ๊ฐ๋ค๋ฉด ์บ์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๊ณ ๋ค๋ฅด๋ค๋ฉด ์ฐ์ฐ์ ์ฌ์ํํ์ฌ ์๋ก์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์ต์ ํ ๊ธฐ๋ฒ์ด๋ค.
๋ฆฌ์กํธ ์ปดํฌ๋ํธ์ Memoization
๊ธฐ๋ณธ์ ์ผ๋ก ๋ฆฌ์กํธ๋
- ๋ด๋ถ state๋ ์ ๋ฌ๋ฐ์ props๊ฐ ๋ณ๊ฒฝ๋ ๋
- ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋ ๋ ๋
shouldComponentUpdate()
๋ฉ์๋์ ๋ฆฌํด๊ฐ ํน์React.memo
์ ๋๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ๋น๊ต ํจ์(ํจ์ํ ์ปดํฌ๋ํธ)์ ๋ฆฌํด๊ฐ์ดtrue
์ผ ๋- ๊ฐ์ ๋ฆฌ๋ ๋๋ฅผ ํ ๋
state์ props์ ์
๋ฐ์ดํธ ์ฌ๋ถ๋ฅผ Object.is()
๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์์ ๋น๊ต(๋ฐ์ดํฐ๊ฐ ๊ฐ์ฒด, ๋ฐฐ์ด, ํจ์์ ๊ฐ์ ์ฐธ์กฐํ์ธ ๊ฒฝ์ฐ ๋ฐ์ดํฐ ์์ฒด๊ฐ ์๋, ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๊ฐ์ ๋น๊ต)ํ์ฌ ์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง์ ํ๋ค.
ํญ๋ชฉ์ ํด๋ฆญํ๋ฉด ์ทจ์์ ์ด ๊ทธ์ด์ง๋ ๊ฐ๋จํ To-Do ๋ฆฌ์คํธ๋ฅผ ์๋ก ๋ค์ด๋ณด์.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [list, setList] = useState([
{
id: 1,
title: "item 1",
checked: true
},
{
id: 2,
title: "item 2",
checked: false
},
{
id: 3,
title: "item 3",
checked: false
}
]);
const handleClick = (id) => {
setList(
list.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
)
);
};
return <List list={list} handleClick={handleClick} />;
}
function List({ list, handleClick }) {
return (
<ul>
{list.map((item) => (
<Item key={item.id} item={item} handleClick={handleClick} />
))}
</ul>
);
}
function Item({ item, handleClick }) {
console.log("re-render", item.title);
return (
<li
className={item.checked ? "checked" : undefined}
onClick={() => handleClick(item.id)}
>
{item.title}
</li>
);
}
์์ง์ ๋ฉ๋ชจ์ด์ ์ด์ ์ ์ํ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์๊ธฐ ๋๋ฌธ์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ ํด๋ฆญํ์์๋ ์๋์ฒ๋ผ ๋ชจ๋ ํญ๋ชฉ๋ค์ด ๋ฆฌ๋ ๋๋ง ๋์ด ์ฝ์์ ์ฐํ๋ ์ํ์ด๋ค.
์ด๋ ์ต์์ ๋ถ๋ชจ App ์ปดํฌ๋ํธ์์ Item ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๊ฒ์ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ state๊ฐ ๋ณ๊ฒฝ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ๋ถ๋ชจ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋ ๋จ์ ๋ฐ๋ผ ๋ชจ๋ ์์ Item ์ปดํฌ๋ํธ๋ค์ด ๋ฆฌ๋ ๋๋ง ๋๋ ๊ฒ์ด๋ค. ์ด์ฒ๋ผ state๋ props๊ฐ ๋ณํ์ง ์์์์๋ ๋ฆฌ๋ ๋๋ง ๋๋ ๊ฒ์ ์กฐ๊ธ ๋ถํ์ํด ๋ณด์ธ๋ค. ๋ง์ฝ ์์์ ๊ฐ์ด ์์ฃผ ์์ ๊ท๋ชจ๊ฐ ์๋๋ผ ํญ๋ชฉ์ด ์ ์ฒ, ์ ๋ง ๊ฐ๊ฐ ๋๋ ๋ฌด๊ฑฐ์ด ์ดํ๋ฆฌ์ผ์ด์ ์ด๋ผ๋ฉด ์ฑ๋ฅ์๋ ์ํฅ์ ๋ผ์น ๊ฒ์ด๋ค.
์ปดํฌ๋ํธ์ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ ์ฉํ๋ฉด ์ด๋ฐ ๋ถํ์ํ ๋ ๋๋ง๊ณผ ๊ทธ๋ก ์ธํ ์ฑ๋ฅ ์ ํ๋ฅผ ๊ฐ์ ํ ์ ์๋ค. ๋ณต์ตํ์๋ฉด, ๋ฉ๋ชจ์ด์ ์ด์
์ ๋์ผํ ์ฐ์ฐ์ด ๋ฐ๋ณต๋ ๋ ์
๋ ฅ๊ฐ์ด ์ ๊ณผ ๊ฐ๋ค๋ฉด ์ด์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ๋ฐํํ๋ ๊ธฐ์ ์ด๋ค. ํจ์ํ ์ปดํฌ๋ํธ์์๋ ์ด๋ฅผ React.memo
์ useCallback()
Hook์ ์ฌ์ฉํด ๊ตฌํํ ์ ์๋ค.
React.memo
React.memo
๋ ์ปดํฌ๋ํธ์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ ์ปดํฌ๋ํธ๋ก ๋ณํํด์ฃผ๋ ๊ณ ์ฐจ ์ปดํฌ๋ํธ(HOC)์ด๋ค. ๋ณํ๋ ์ปดํฌ๋ํธ๋ ์ ๋ฌ๋ฐ์ props์ ์์ ๋น๊ต๋ฅผ ํตํด, ์ ๊ณผ ๊ฐ๋ค๋ฉด ์ด์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ๋ค.
์๋์ฒ๋ผ React.memo
์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ฉ๋ชจ์ด์ ์ด์
ํ๊ณ ์ถ์ ์ปดํฌ๋ํธ๋ฅผ ์ ๋ฌํ๋ค. ๋ง์ฝ ์์ ๋น๊ต๊ฐ ์๋ ๋ค๋ฅธ ๋น๊ต๋ฅผ ์ํ๋ค๋ฉด, ๋ ๋ฒ์งธ ์ธ์๋ก ๋ณ๋๋ก ์์ฑํ ๋น๊ต ํจ์๋ฅผ ์ ๋ฌํ๋ค.
function ComponentToBeMemoized(props) {
// ๋ ๋๋ง
}
function areEqual(prevProps, nextProps) {
// ๋น๊ต ๋ก์ง
}
export default React.memo(ComponentToBeMemoized, areEqual);
areEqual
ํจ์๋ props๋ค์ด ๊ฐ์ผ๋ฉด true
๋ฅผ ๋ฐํํ๊ณ , ๋ค๋ฅด๋ฉด false
๋ฅผ ๋ฐํํ๋ค. ๋น๊ต ํจ์์ ๋ฆฌํด๊ฐ์ด true
์ผ ๋๋ props๊ฐ ๋ณํ์ง ์์๋ค๊ณ ํ๋จํ์ฌ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ๋๋ก ํ๋ ๋ฐฉ์์ด๋ค.
๋ฐ๋ฉด์ ํด๋์คํ ์ปดํฌ๋ํธ์ shouldComponentUpdate()
๋ฉ์๋๋ props๋ค์ด ๊ฐ์ผ๋ฉด false
๋ฅผ ๋ฐํํ๊ณ , ๋ค๋ฅด๋ฉด true
๋ฅผ ๋ฐํํ๋ค. ๋ฐ๋ผ์ ๋ฆฌํด๊ฐ์ด true
์ผ ๋ props๊ฐ ๋ฌ๋ผ ์๋กญ๊ฒ ์ฐ์ฐํด์ผ ํ๋ค๊ณ ํ๋จํ์ฌ ๋ฆฌ๋ ๋๋ง ํ๋ ๋ฐฉ์์ด๋ค. (ํจ์ํ ์ปดํฌ๋ํธ์ ์ ๋ฐ๋์ ๋ฐฉ์์ด๋ ์ ์ํ ๊ฒโ )
๊ทธ๋ผ ์ด์ Item ์ปดํฌ๋ํธ์ ์ ์ฉํด๋ณด์. ์ปดํฌ๋ํธ๋ฅผ ์ ์ธํ๋ฉด์ ๋์์ React.memo
๋ฅผ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด ์๋์ฒ๋ผ ํจ์ ํํ์์ผ๋ก ์์ฑํ๋ค.
const Item = React.memo(({ item, handleClick }) => {
console.log("re-render", item.title);
return (
<li
className={item.checked ? "checked" : undefined}
onClick={() => handleClick(item.id)}
>
{item.title}
</li>
);
});
ํ์ง๋ง ์์ง๋ ์ฌ์ ํ ๋ชจ๋ ํญ๋ชฉ๋ค์ด ๋ฆฌ๋ ๋๋ง ๋๋ค?! ์ ์์ด๋ค. ๊ทธ ์์ธ์ ํจ์์ ํน์ง์ ์๋ค.
ํจ์ํ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๋ค๋ ๊ฑด ํด๋น ํจ์๊ฐ ๋ค์ ํธ์ถ๋๋ค๋ ๋ป์ด๋ค. ๊ทธ๋ฆฌ๊ณ ํจ์๋ ํธ์ถ๋์ด ์คํ๋ ๋๋ง๋ค ๋ด๋ถ์ ์ ์ธ๋ ๋ณ์๋ค๊ณผ ํจ์๋ค ๋ํ ๋ค์ ์ ์ธํ์ฌ ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ํ ๋นํ๋ ํน์ง์ ๊ฐ์ง๋ค. ์ธ์คํด์ค๊ฐ ์์ฑ๋ ์ดํ์๋ ๋ฆฌ๋ ๋๋ง์ด ๋์ด๋ ๋ฉค๋ฒ ๋ณ์๋ ๋ฉ์๋๋ค์ด ์ฌ์ ์ธ ๋์ง ์๋ ํด๋์ค์๋ ๋น๊ต๋๋ค.
์ด๋ฌํ ์๋ฆฌ๋ก ๋ถ๋ชจ App ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋ ๋๋ง๋ค ๋ด๋ถ์ handleClick
ํจ์๊ฐ ์ฌ์ ์ธ๋๊ธฐ ๋๋ฌธ์, Item ์ปดํฌ๋ํธ๋ค์ ํญ์ ์๋ก์ด ์ฐธ์กฐ๊ฐ์ ๊ฐ๋ handleClick
props๋ฅผ ์ ๋ฌ๋ฐ๊ฒ ๋๊ณ , ๋ฉ๋ชจ์ด์ ์ด์
ํด๋ ์ด์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ์ง ๋ชปํ๋ ๊ฒ์ด๋ค. ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ React.memo
๋ฅผ ์ ํํ๊ฒ ์๋์ํค๊ธฐ ์ํด์๋ ๋ด๋ถ ํจ์ ๋ํ ๋ฉ๋ชจ์ด์ ์ด์
์ ํด์ค์ผ ํจ์ ์ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ useCallback()
Hook์ ์ฌ์ฉํ์ฌ ๊ตฌํํ ์ ์๋ค.
โ
React.memo
๋ props ๋ณํ์๋ง ์ํฅ์ ๋ฐ๋๋ค. ๋ง์ฝReact.memo
๋ก ๊ฐ์ธ์ง ์ปดํฌ๋ํธ ๋ด๋ถ์์useState
,useReducer
,useContext
Hook์ ์ฌ์ฉํ๋ค๋ฉด, state๋ context๊ฐ ๋ณํ ๋๋ง๋ค ์์ ์ธ๊ธํ ๋ฆฌ๋ ๋ ์กฐ๊ฑด(1. ๋ด๋ถ state๋ ์ ๋ฌ๋ฐ์ props๊ฐ ๋ณ๊ฒฝ๋ ๋)์ ๋ถํฉํ์ฌ ๋ค์ ๋ ๋๋ง ๋ ๊ฒ์ด๋ค.
useCallback()
useCallback()
Hook์ ๋ฉ๋ชจ์ด์ ์ด์
๋ ์ฝ๋ฐฑํจ์๋ฅผ ๋ฐํํ๋ ๋ฆฌ์กํธ์ ๋ด์ฅ API์ด๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๋ก ํจ์๋ฅผ, ๋ ๋ฒ์งธ ์ธ์๋ก ์์กด์ฑ ๋ฐฐ์ด์ ์ ๋ฌ๋ฐ๋๋ค.
import React, { useCallback } from 'react';
const memoizedCallback = useCallback(() => {
// Do something with a
}, [a]);
์์กด์ฑ์ด ๋ณ๊ฒฝ๋์ ๋๋ง ๋ณ๊ฒฝ๋ ์์กด์ฑ์ ์ฐธ์กฐํ๋ ํจ์๊ฐ ์๋กญ๊ฒ ์ ์ธ๋์ด ๋ฐํ๋ ๊ฒ์ด๊ณ , ๊ทธ๋ ์ง ์๋ค๋ฉด ๋ฉ๋ชจ์ด์ ์ด์ ๋ ํจ์๊ฐ ์ฌ์ฌ์ฉ๋ ๊ฒ์ด๋ค.
const handleClick = useCallback(
(id) => {
setList(
list.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
)
);
},
[list]
);
ํ์ง๋ง ์์ง ํจ์ ์ด ํ๋ ๋ ๋จ์์๋ค.
์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์์กด์ฑ ๋ฐฐ์ด๋ก list
๋ฅผ ์ ๋ฌํ๊ณ ์๋ค. list
๋ ๊ฐ ํญ๋ชฉ์ ๋๋ฅผ ๋๋ง๋ค ๋งค๋ฒ ๋ฐ๋๋ ๋ฐ์ดํฐ์ด๋ค. ๋ฐ๋ผ์ handleClick
ํจ์๋ ๋งค๋ฒ ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ ์ฃผ์์ ํ ๋น๋ ๊ฒ์ด๊ณ , ๊ฒฐ๊ตญ ์ด์ ๊ณผ ๊ฐ์ด ๋ชจ๋ Item ์ปดํฌ๋ํธ๋ค์ด ๋ฆฌ๋ ๋ ๋ ๊ฒ์ด๋ค. ์ด ๋ฌธ์ ๋ ์๋์ ๊ฐ์ด ํจ์ํ ์
๋ฐ์ดํธ๋ก ํด๊ฒฐํ ์ ์๋ค.
const handleClick = useCallback((id) => {
setList((list) =>
list.map((item) =>
item.id === id ? { ...item, checked: !item.checked } : item
)
);
}, []);
์
๋ฐ์ดํธ ํจ์๋ ์ธ์๋ก ํจ์๋ฅผ ์ ๋ฌํ ์๋ ์๋๋ฐ, ์ด ํจ์๋ ์๋์ ์ผ๋ก ์ต์ ์ํ๋ฅผ ์ธ์๋ก ์ ๋ฌ๋ฐ๋๋ค. ๋ฐ๋ผ์ useCallback()
์์กด์ฑ ๋ฐฐ์ด์ list
๋ฅผ ์ถ๊ฐํ์ง ์์๋ ๊ฐ์ฅ ์ต์ list
๋ฅผ ์ฐธ์กฐํ๋ handleClick
ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ ์ ์๊ฒ ๋๋ค.
์ต์ข ์ฝ๋์ด๋ค. ์ฑ๊ณต์ ์ผ๋ก Item ์ปดํฌ๋ํธ์ ๋ฉ๋ชจ์ด์ ์ด์ ์ด ๋๊ณ ์๋ค. ์ฑ๊ณต์ ๐๐