1. Memoization ?
Memoization은 컴퓨터 프로그램이 동일한 계산을 반복해야할 때, 이전 계산한 값을 메모리에 저장함으로 동일한
계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다.
2. React의 Memoization ?
1. memo
동일한 props로 렌더링을 한다면, React.memo를 사용하면 성능 향상을 누릴 수 있다.
memo를 사용하면 React는 컴포넌트를 렌더링하지 않고, 마지막으로 렌더링된 결과를 재사용한다.
import React, { useState, useEffect } from "react";
import MemoItem from "./MemoItem";
const commentList = [
{ title: "comment1", content: "message1", likes: 1 },
{ title: "comment2", content: "message2", likes: 1 },
{ title: "comment3", content: "message3", likes: 1 },
{ title: "comment4", content: "message4", likes: 1 },
{ title: "comment5", content: "message5", likes: 1 },
{ title: "comment6", content: "message6", likes: 1 },
];
const Comments = ({ commentList }) => {
return (
<div>
{commentList.map((comment) => (
<MemoItem
key={comment.title}
title={comment.title}
content={comment.content}
likes={comment.likes}
/>
))}
</div>
);
};
const Memo = () => {
const [comments, setComments] = useState(commentList);
useEffect(() => {
const interval = setInterval(() => {
setComments((prevComment) => [
...prevComment,
{
title: `comment${prevComment.length + 1}`,
content: `message${prevComment.length + 1}`,
likes: 1,
},
]);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <Comments commentList={comments} />;
};
export default Memo;
1초에 댓글이 하나씩 추가되는 예시를 만들어봤다.
useEffect()에서 setInterval를 사용해서 1초마다 setComments의 배열에 하나씩 추가하는 방식이다.
import React, { memo, Profiler } from "react";
const MemoItem = ({ title, content, likes }) => {
return (
<Profiler
id="CommentItem"
onRender={(
id,
phase,
actualDuration,
baseDuration,
startTime,
comitTime,
interactions
) => {
console.log(`actualDuration : ${actualDuration}`);
}}
>
<div
style={{
borderBottom: `1px solid grey`,
padding: `10px`,
cursor: `pointer`,
}}
>
<span>{title}</span>
<br />
<span>{content}</span>
<br />
<span>{likes}</span>
</div>
</Profiler>
);
};
export default memo(MemoItem);
중요한 곳은 이곳으로 memo() 를 사용하지 않는다면, 댓글이 업데이트 될 때마다 모든 MemoItem 컴포넌트가 다시
렌더링이 발생한다.
memo()를 사용한다면, 이미 있는 MemoItem은 그대로 사용하고 새롭게 추가된 MemoItem만 렌더링을 하게 된다.
※ Profiler ?
중간에 특이한 태그를 봤을 건데, 해당 기능은 React에서 제공하는 성능 측정 API로 태그 안에 있는 내용의 렌더링에
걸리는 시간을 측정한다.
2 useCallback
만약 부모에게서 props로 함수를 받는다면 어떻게 될까??
import React, { useState, useEffect } from "react";
import MemoComments from "./MemoComments";
const commentList = [
{ title: "comment1", content: "message1", likes: 1 },
{ title: "comment2", content: "message2", likes: 1 },
{ title: "comment3", content: "message3", likes: 1 },
{ title: "comment4", content: "message4", likes: 1 },
{ title: "comment5", content: "message5", likes: 1 },
{ title: "comment6", content: "message6", likes: 1 },
];
const Memo = () => {
const [comments, setComments] = useState(commentList);
useEffect(() => {
const interval = setInterval(() => {
setComments((prevComment) => [
...prevComment,
{
title: `comment${prevComment.length + 1}`,
content: `message${prevComment.length + 1}`,
likes: 1,
},
]);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <MemoComments commentList={comments} />;
};
export default Memo;
최 상위 부모로 1초에 1번씩 댓글을 만들어내고 있다.
import React from "react";
import MemoItem from "./MemoItem";
export default function MemoComments({ commentList }) {
const clickHandler = () => {
alert("눌렸음!");
};
return (
<div>
{commentList.map((comment) => (
<MemoItem
key={comment.title}
title={comment.title}
content={comment.content}
likes={comment.likes}
onClick={clickHandler}
/>
))}
</div>
);
}
두 번째로 MemoItem에게 click 함수를 만들어서 전달해주고 있다.
import React, { memo, Profiler } from "react";
const MemoItem = ({ title, content, likes, onClick }) => {
return (
<Profiler
id="CommentItem"
onRender={(
id,
phase,
actualDuration,
baseDuration,
startTime,
comitTime,
interactions
) => {
console.log(`actualDuration : ${actualDuration}`);
}}
>
<div
style={{
borderBottom: `1px solid grey`,
padding: `10px`,
cursor: `pointer`,
}}
onClick={onClick}
>
<span>{title}</span>
<br />
<span>{content}</span>
<br />
<span>{likes}</span>
</div>
</Profiler>
);
};
export default memo(MemoItem);
memo를 통해서 props의 변화가 없다면 리렌더링되지않고 기존에 있는 MemoItem은 그대로 사용하게 만들었다.
결과를 말하자면, 모든 MemoItem이 다시 리렌더링된다.
분명 props가 동일한 경우 memo를 통해 재사용하게 만들었는데 왜 이런 결과가 발생한걸까?
export default function MemoComments({ commentList }) {
const clickHandler = () => {
alert("눌렸음!");
};
return (
<div>
{commentList.map((comment) => (
<MemoItem
key={comment.title}
title={comment.title}
content={comment.content}
likes={comment.likes}
onClick={clickHandler}
/>
))}
</div>
);
}
MemoIteml의 부모인 MemoComments의 props인 commentList가 1초당 댓글이 추가되므로 변경되기때문에
부모 자체가 리렌더링되고있어서 발생하는 문제다.
부모가 리렌더링되기 때문에 clickHandler라는 함수가 1초마다 새롭게 만들어지고 있어서 MemoItem에게 넘기는
props에 변화가 생겨 memo를 사용하더라도 전체가 리렌더링되는 것이다.
문제 해결을 위해서 사용되는 것이 useCallback이다.
const clickHandler = useCallback(() => {
alert("눌렸음!");
}, []);
useCallback은 dependency Array가 변경될 경우에만 함수를 새롭게 만들어주는 Hook이다.
그렇기때문에 commentList가 변경되더라도 dependency Array 안에 commentList가 없기때문에 함수는 새롭게 만들어
지지 않는다.
3. useMemo
사이트를 만들다가 useState 같이 동적으로 변화하는 값을 다루는 일은 많이 한다.
import React, { memo, Profiler, useMemo, useState } from "react";
const MemoItem = ({ title, content, likes, onClick }) => {
const [clickCount, setClickCount] = useState(0);
const rate = () => {
console.log("???");
return likes > 10 ? "Good" : "bad";
};
return (
<Profiler
id="CommentItem"
onRender={(
id,
phase,
actualDuration,
baseDuration,
startTime,
comitTime,
interactions
) => {
console.log(`actualDuration : ${actualDuration}`);
}}
>
<div
style={{
borderBottom: `1px solid grey`,
padding: `10px`,
cursor: `pointer`,
}}
onClick={() => {
onClick();
setClickCount((prevCount) => prevCount + 1);
}}
>
<span>{title}</span>
<br />
<span>{content}</span>
<br />
<span>{likes}</span>
<br />
<span>{rate()}</span>
</div>
</Profiler>
);
};
export default memo(MemoItem);
기존 코드에서 clickCount 라는 조회 기능의 state를 만들고, onClick에서 누를 경우 +1 시켜주는 기능을 만들었다.
그리고 추가로 rate()라는 함수를 만들었는데,
const rate = () => {
console.log("???");
return likes > 10 ? "Good" : "Bad";
};
likes를 확인해서 10 이상이면 Good을 리턴, 이하면 Bad를 리턴하게 하였다.
여기서 우리가 댓글중 하나를 클릭하면 해당 댓글의 rate 함수가 다시 호출된다.
onClick과 rate의 접점은 하나도 없지만, onClick에서 clickCount를 변경해서 해당 컴포넌트가 리렌더링이 발생했다.
당연히 state가 바뀌기 때문에 리렌더링 되는것은 당연하다. 하지만, rate 함수가 다시 호출될 필요는 없는 것이다.
이것을 막아주는 기능이 useMemo이다.
const rate = useMemo(() => {
console.log("???");
return likes > 10 ? "Good" : "bad";
}, [likes]);
useMemo의 dependency Array 안에 있는 값이 변경될 때만 다시 호출되게 하는 Hook이다.
useCallback과 useMemo가 헷갈릴텐데,
useCallback은 return 을 통해서 값을 얻는 함수가 아닌 경우 사용을 하고, useMemo는 return을 통해 값을 얻을때 사용한다.
'React > 패스트캠퍼스' 카테고리의 다른 글
[React - 기초] Render Props (1) | 2022.03.18 |
---|---|
[React - 기초] Portals (2) | 2022.03.17 |
[React - 기초] Hooks (0) | 2022.03.10 |
[React - 기초] React와 리랜더링 (0) | 2022.03.09 |
[React - 기초] JSX, Babel (0) | 2022.03.08 |