이 글은 팀스파르타 리액트 숙련 학습 자료 강의를 정리한 글입니다
렌더링이 빈번하게 일어나는 것은 좋지 않다 > cost, 비용이 많이 든다.
컴포넌트를 메모리에 저장해두고(캐싱) 필요할 때 갖다 쓰는 것을 말한다.
이렇게 되면 부모 컴포넌트의 state 변경에 의해 props 변경이 일어나지 않는 이상,
컴포넌트는 리렌더링 되지 않는다.
이것을 컴포넌트의 memoization 이라고 한다.
즉, React.memo는 컴포넌트를 기억(memoization)하는 것이다!
export default React.memo(Box1);
자식 컴포넌트 export에 React.memo를 붙혀주기만 하면 된다.
useCallback은 함수를 기억하는 것이다!
만약 카운트를 초기화해주는 함수를 만들고, 그것을 props로 보냈다고 하자.
// count를 초기화해주는 함수
const initCount = () => {
setCount(0);
};
return (
<>
<h3>카운트 예제입니다!</h3>
<p>현재 카운트 : {count}</p>
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
<div style={boxesStyle}>
<Box1 initCount={initCount} />
</div>
</>
);
}
function Box1({ initCount }) {
console.log("Box1이 렌더링되었습니다.");
const onInitButtonClickHandler = () => {
initCount();
};
return (
<div style={boxStyle}>
<button onClick={onInitButtonClickHandler}>초기화</button>
</div>
);
}
export default React.memo(Box1);
리액트 메모를 통해 메모이제이션을 했는데도 리렌더링이 되는 이유는,
App.jsx가 리렌더링 되면서
초기화하는 initCount 코드가 다시 만들어지면서,
onInitButtonClickHandler코드도 다시 만들어지기 때문이다.
자바스크립트에서는 함수도 객체의 한 종류기 때문에, 모양은 같더라도 다시 만들어지면 주소값이 달라지게 된다.
// 변경 전
const initCount = () => {
setCount(0);
};
// 변경 후
const initCount = useCallback(() => {
setCount(0);
}, []);
이렇게 만들면 리렌더링이 되지 않는다.
initCount 함수를 조금 바꿔보자
const initCount = useCallback(() => {
console.log(`${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, []);
count의 값을 출력해주는 기능을 추가한다고 하자.
이 기능을 실행해보면, 실제로는 count는 올라가든 내려가든 0으로밖에 보이지 않는다.
이렇게 나오는 이유는 useCallback이 count가 0일 때의 시점을 기준으로 메모리에 함수를 저장했기 때문이다.
이 경우에, []
의존성 배열 안에 count를 넣으면, count가 바뀔 때마다 새롭게 함수를 할당하게 된다.
const initCount = useCallback(() => {
console.log(`${count}에서 0으로 변경되었습니다.`);
setCount(0);
}, [count]);
useMemo는 함수가 리턴하는 '값' 그 자체를 기억하는 것이다
동일한 값을 반환하는 함수를 계속 호출해야 한다면, 필요없는 렌더링을 하는 것이다.
const value = useMemo(()=> {
return 반환할_함수()
}, [dependencyArray]);
dependencyArray 값이 변경될 때만 반환할 함수가 호출된다.
그렇지 않을 경우 값을 가져오기만 한다.
아래와 같은 코드가 있다.
import React, { useEffect, useState } from "react";
function ObjectComponent() {
const [isAlive, setIsAlive] = useState(true);
const [uselessCount, setUselessCount] = useState(0);
const me = {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
useEffect(() => {
console.log("생존여부가 바뀔 때만 호출해주세요!");
}, [me]);
return (
<>
<div>
내 이름은 {me.name}이구, 나이는 {me.age}야!
</div>
<br />
<div>
<button
onClick={() => {
setIsAlive(!isAlive);
}}
>
누르면 살았다가 죽었다가 해요
</button>
<br />
생존여부 : {me.isAlive}
</div>
<hr />
필요없는 숫자 영역이에요!
<br />
{uselessCount}
<br />
<button
onClick={() => {
setUselessCount(uselessCount + 1);
}}
>
누르면 숫자가 올라가요
</button>
</>
);
}
export default ObjectComponent;
useEffect hook을 이용해서 me의 정보가 바뀌었을 때만 발동되게끔 dependency array를 넣어놨는데요. 엉뚱하게도 count를 증가하는 button을 눌러보면 계속 log가 찍힌다.
그 이유는 state가 바뀌게 되면 리렌더링이 되고, 컴포넌트 함수가 새로 호출되며,
me 객체도 다시 할당되게 되는데 이 때 다른 메모리 주소값을 할당받게 되어
me가 달라졌다고 생각하기 때문이다.
// 이건 일치해요
const a = 1;
const b = 1;
console.log(a === b); // true
// 하지만 이건 달라요
const me = {
name: "ted chang",
age: 21,
};
const you = {
name: "ted chang",
age: 21,
};
console.log(me === you); // false
이런 현상을 해결하기 위해 useMemo를 사용할 수 있다.
const me = useMemo(() => {
return {
name: "Ted Chang",
age: 21,
isAlive: isAlive ? "생존" : "사망",
};
}, [isAlive]);
최적화의 원리는 바뀌지 않는 것을 메모리에 저장하는 것이므로
최적화를 너무 남발하게 되면 별도의 메모리 확보를 너무 많이 하기 되기 때문에
오히려 성능이 악화될 수 있다. 필요할 때만 쓸것!!!