React.memo()
🔥함수형 컴포넌트 기반으로 정리를 했습니다!
❓불필요한 렌더링 발생
부모 컴포넌트의 상태나 프롭스가 변경되면 해당 부모 컴포넌트에 속한 자식 컴포넌트도 재평가 된다.
> 자식 컴포넌트에게 내려주는 props의 값이 변경되지 않아도, 부모 컴포넌트의 상태가 변경되었기 때문에 자식 컴포넌트도 리렌더링 된다.
>> 불필요한 리렌더링은 컴포넌트 계층 구조가 복잡하고 데이터가 자주 변경될 때 발생할 수 있다.
불필요한 재평가를 방지해야 한다!
❗️React.memo()를 사용하면 된다!
컴포넌트 export 할 때, React.memo()를 사용해서 컴포넌트를 감싸서 내보낸다.
함수형 컴포넌트를 메모이제이션하여 이전에 전달된 props가 변경되지 않으면, 이전 결과를 재사용하기 때문이다.
🔥메모이제이션
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술.
export default React.memo(컴포넌트명);
===> 부모 컴포넌트가 바뀌어도 전달 받은 props에 변경 사항이 없다면, 이전에 렌더링된 결과를 재사용한다.
===> 해당 컴포넌트에 속한 자식 컴포넌트에 대해서도 적용된다.
📌 이렇게 좋은 걸(?) 왜 모든 컴포넌트에 적용하지 않는걸까?
===> 최적화에는 비용이 따르기 때문에
🌀React.memo() 사용 시 아래의 두 가지 작업이 수행된다.
- React.memo()는 내부에서 이전 렌더링 시점에서의 자식 컴포넌트의 props 값을 메모리에 저장한다.
=> 이전 props 값은 새로운 렌더링이 발생할 때마다 비교에 사용된다. - React.memo()는 리렌더링이 필요한지 여부를 판단하기 위해 현재 자식 컴포넌트의 props 값과 이전에 저장된 이전 props 값을 비교한다.
=> 이 비교 작업은 얕은 비교(shallow comparison)를 사용하며, 객체나 배열의 참조를 비교하여 값이 변경되었는지 여부를 확인한다.
모든 컴포넌트에 React.memo()를 사용하는 것은 모든 업데이트에 대해 리렌더링을 방지하므로, 경우에 따라서는 오히려 성능에 악영향을 줄 수 있다.
결국, React.memo()의 사용은 컴포넌트를 리렌더링할지 재평가하는 데에 필요한 성능 비용과 props를 비교하는 성능 비용을 서로 맞바꾸는 것이다.
📌 언제 사용할까?
React.memo()의 효율성은 컴포넌트의 복잡도와 props 갯수, 컴포넌트 트리의 깊이, 부모 컴포넌트의 특성(props 변경 여부 등)에 따라 달라진다.
컴포넌트가 복잡하고 재평가에 필요한 성능 비용이 높은 경우에는 React.memo()를 사용하여 자식 컴포넌트의 불필요한 리렌더링을 방지함으로써 재평가에 필요한 성능 비용을 줄일 수 있다.
반대로
부모 컴포넌트를 매번 재평가할 때마다 컴포넌트의 변화가 있거나 props의 값이 변화할 수 있는 경우라면 React.memo()의 사용은 크게 의미가 없다. 어떻게든 컴포넌트 리렌더링을 할거니까
🔥어떤 컴포넌트를 최적화하느냐에 따라서는 이 두 성능 비용을 적절하게 균형잡아 사용하는 것이 중요하다.
📌 React.memo()의 props 데이터형
- React.memo()를 사용하는 컴포넌트에 전달되는 props가 원시형이면 React.memo()는 잘 작동된다.
- React.memo()를 사용하는 컴포넌트에 전달되는 props가 참조형이면 React.memo()는 작동하지 않는다.
- React.memo()는 얕은 비교(shallow comparison)를 사용하여 이전에 렌더링된 컴포넌트의 props와 현재 렌더링된 컴포넌트의 props를 비교한다.
- 전달되는 props가 참조형이면, 해당 참조형이 업데이트되더라도 참조가 변경되지 않으면 React.memo()는 해당 컴포넌트를 다시 렌더링하지 않는다.
- 얕은 비교는 객체나 배열의 참조만을 확인하므로 내부의 값이 변경되더라도 비교에서 무시되기 때문이다.
// 부모 컴포넌트 Parent.js
import React, { useState } from 'react';
const Parent = () => {
const [message, setMessage] = useState("Hello, React!");
// 부모 컴포넌트의 상태를 업데이트하는 함수
const updateMessage = () => {
setMessage("Updated message!");
};
return (
<>
{/* 메모이제이션된 컴포넌트 사용 */}
<MemoizedComponent message={message} />
{/* 버튼을 클릭하면 부모 컴포넌트의 상태를 업데이트 */}
<button onClick={updateMessage}>Update Message</button>
</>
);
};
export default Parent;
// 자식 컴포넌트 MemoizedChild.js
import React from 'react';
// React.memo()를 사용하여 메모이제이션된 함수형 컴포넌트
const MemoizedChild = React.memo(({ message }) => {
return (
<>
<p>{message}</p>
</>
);
});
export default MemoizedChild;
❓그렇다면 React.memo()는 props를 통해 참조형을 받는 컴포넌트에는 적용할 수 없을까?
❗️전달받는 객체를 직접 변경하는 대신, 새로운 객체를 생성하여 참조를 갱신하는 방법을 사용해 보자!
obj.name = 'Jane'; 처럼 직접 name을 변경하는 대신 상태 관리 훅인 useState를 사용하여 객체를 생성하고, 이전 상태를 사용하여 객체 업데이트를 수행한다.
// 부모 컴포넌트 Parent.js
import React, { useState } from 'react';
const Parent = () => {
// 기존 객체
// const obj = { name: 'John' };
// reactive 한 객체 생성
const [obj, setObj] = useState({ name: 'John' });
const updateObject = () => {
// 기존 참조를 변경하지 않고 내용만 업데이트하면
// React.memo()에서는 변경된 것으로 감지되지 않음
// obj.name = 'Jane';
// 기존 참조를 변경하지 않고 새로운 객체를 생성하여 적용
setObj(prevObj => ({ ...prevObj, name: 'Jane' }));
};
return (
<>
<ChildComponent objProp={obj} />
<button onClick={updateObject}>Update Object</button>
</>
);
};
export default Parent;
이 방법 외에 다음 포스트의 useCallback() 훅을 사용해서 React.memo()를 효율적으로 사용할 수 있다.
🔥useCallback() 훅 => 컴포넌트 실행 전반에 걸쳐 함수를 저장할 수 있게 하는 훅