[React] React에서의 최적화 1: react.memo 정리 (feat. 리랜더링 방지)
react.memo란?
memo를 사용하면 컴포넌트의 props가 변경되지 않은 경우 리렌더링을 건너뛸 수 있습니다. - react 공식문서
일반적으로 부모 컴포넌트가 리랜더링 되면 자식 컴포넌트도 리랜더링 된다.
props 받든 안받든, props가 변경이 되든 안되든...
하지만 state가 변경되지도 않았는데, 리랜더링이 되면 성능적으로 낭비이기 때문에
컴포넌트의 props가 변경 되지 않은 경우에는 리랜더링을 하지 않도록 memoize한다.
이때 사용 할 수 있는게 react.memo이다.
※ memoize란?
props와 동일하다면 부모가 리렌더링될 때 새로운 props가 이전 props와 동일하면 리렌더링 되지 않는 컴포넌트를
만들 수 있습니다. 이러한 컴포넌트를 memoized 상태라고 합니다.
결론 : props가 변경되었을 때만 컴포넌트가 리랜더링 되도록 최적화를 해주는 함수이다!
react.memo 사용방법
기본문법
memo(Component, option)
래퍼런스
component: memoize 하려는 컴포넌트입니다. memo는 이 컴포넌트를 수정하지 않고 대신
새로운 memoized 컴포넌트를 반환합니다.
option: 컴포넌트의 이전 props와 새로운 props의 두 가지 인수를 받는 함수입니다.
이전 props와 새로운 props가 동일한 경우, 즉 컴포넌트가 이전 props와 동일한 결과를 렌더링하고
새로운 props에서도 이전 props와 동일한 방식으로 동작하는 경우 true를 반환해야 합니다.
그렇지 않으면 false를 반환해야 합니다. 일반적으로 이 함수를 지정하지 않습니다.
샘플코드
App.jsx
import { useState } from "react";
import MemoComponent from "./components/MemoComponent";
const App = () => {
console.log("App 리랜더링!!");
const [str, setStr] = useState("메모메모");
const [number, setNumber] = useState(0);
const changeNumber = () => {
setNumber(number + 1);
};
return (
<>
<div className="App">
<MemoComponent str={str} />
<h1>{number}</h1>
<button onClick={changeNumber}>+</button>
</div>
</>
);
};
export default App;
MemoComponent.jsx
import { memo } from "react";
const MemoComponent = ({ str }) => {
console.log("memo 리랜더링!!");
return (
<>
<div>
<h1>{str}</h1>
</div>
</>
);
};
/**
* 아래와 같이 따로 변수로 할당하여 export 해도 됨..
* const MemoizedComponent = memo(MemoComponent);
* export default MemoizedComponent;
*/
export default memo(MemoComponent);
이렇게 코드를 작성하였다.
코드를 보면 App 컴포넌트가 props로 str을 자식 컴포넌트에게 넘기고 있다.
이때 App컴포넌트에서 state의 변경이 감지되어 리랜더링이 되더라도(number가 증가)
props로 넘어오는 str의 값이 변경 되지 않으면 자식컴포넌트는 리랜더링 되지 않는다.
즉 MemoComponent는 리랜더링 되지 않는다고 볼 수 있다..
결과
결과를 보면 App이 리랜더링 되고 있지만, 자식 컴포넌트인 MemoComponent는 리랜더링 되지 않는 게 보인다.
memo함수를 통해 최적화를 했기 때문이다..
react.memo 사용시 주의사항
react.memo함수의 기본 동작방식은 이전 props와 현재 props를 비교하여
양쪽의 값이 같은 경우에는 리랜더링을 하지 않는다...
문제는 react.memo가 얕은비교를 한다는 점인데, 원시타입의 데이터 같은 경우에는 상관이 없지만
참조타입(객체, 배열, 함수..)의 경우에는 값이 똑같더라도, 부모컴포넌트가 변경되면 참조값이 변경되기 때문에
memo를 통해 최적화된 컴포넌트라 할지라도 리랜더링 된다는 점이다...
그렇기 때문에 props로 참조타입의 데이터를 넘길 때는 옵션을 사용하거나 useMemo 훅을 사용하여
쓸모없는 리랜더링이 발생하지 않도록 해야한다..
샘플코드1
export default memo(MemoComponent, (prevProps, nextProps) => {
/**
* return값이 true인 경우 -> 리랜더링X
* return값이 false인 경우 -> 리랜더링O
*/
//Object를 넘겼다는 가정
if(prevProps.id !== nextProps.id) return false;
if(prevProps.name !== nextProps.name) return false;
if(prevProps.age !== nextProps.age) return false;
return true;
});
샘플코드2
const compareFun = (prevProps, nextProps) => {
/**
* return값이 true인 경우 -> 리랜더링X
* return값이 false인 경우 -> 리랜더링O
*/
//Object를 넘겼다는 가정
if(prevProps.id !== nextProps.id) return false;
if(prevProps.name !== nextProps.name) return false;
if(prevProps.age !== nextProps.age) return false;
return true;
}
export default memo(MemoComponent, compareFun);
위 샘플코드를 보면 memo함수의 2번째 파라메터로 옵션을 넣어주었다.
샘플코드1과 샘플코드2는 코드는 다르지만, 완전히 동일하게 동작한다.
코드에서는 이전 객체와 현재 객체의 속성값을 하나하나 전부 비교하여 값이 같다면 리랜더링을 안하도록
true를 반환해주고, 값이 다른 게 있다면 props가 변경되었다고 판단하여 리랜더링 되도록
fasle를 반환해준다...
뭐.. 근데 아직은 공부 안했지만 useMemo나 usecallback을 사용하면 된다고 하니 나중에 그거나 제대로 정리해야겠다..