본문 바로가기

Personal Posting/ReactJS

React Hook 정리 - useMemo

컴포넌트 최적화를 위해 사용되는 대표적인 Hook인

 

useMemo, useCallback 중 useMemo에 대해 정리해보려고 한다.

 

우선 useMemo를 사용하기에 앞서 아래 용어를 알아야한다.

Memoization: 동일한 값을 리턴하는 함수를 반복적으로 호출할 경우 처음 계산된 값을 메모리에 저장하여 이를 재사용하는 기법

 

함수형 컴포넌트를 사용하는 코드에서 컴포넌트가 렌더링된다는 의미는 해당 컴포넌트 함수가 호출된다는 의미이다. 즉, 호출될때마다 함수 내의 모든 내부 변수를 그때 그때 초기화 한다는 의미이다.

 

일례로 아래와 같은 코드가 있다고 가정하자.

function Component() {
	const value = heavyTask();
	return <div>
		{value}
	</div>
}

function heavyTask() {
	// Very Heavy Work
	return "Finish"
}

컴포넌트가 불릴때 마다 무진장 무거운 작업을 반복적으로 하게된다.

물론 이것이 프로세스상 불가피하게 저렇게 처리해야되는 작업일 경우면 모르겠지만 위 예시에서는 heavyTask는 항상 동일한 결과를 주는 함수이기 때문에 여러번 호출하는 것은 자원 낭비이다.

 

따라서 아래와 같이 useMemo를 사용하여 코드를 변경해보자.

function Component() {
	const value = useMemo(()=> heavyTask(), [])
	return <div>
		{value}
	</div>
}

function heavyTask() {
	// Very Heavy Work
	return "Finish"
}

useMemo를 호출하고 첫 번째 인자에는 우리가 Memoization해서 사용할 콜백함수를 넣어 계산된 값을 반환한다. 두 번째 인자에는 배열이 들어가며 이 곳에 들어가는 값이 업데이트 될 때에만 계산한다. 위와 같이 빈 배열인 경우에는 Mount되었을 때 1번만 호출되게 된다.

 

간단한 개념을 설명하면 위와 같으나 useMemo를 활용하는 또 하나의 좋은 사례가 있다.

이를 설명하기 전에 먼저 아래의 개념을 알아야 한다.

 

// case1
const tmp1 = "blue";
const tmp2 = "blue";
console.log(tmp1 === tmp2); // true

// case2
const tmp3 = {color : "blue"};
const tmp4 = {color : "blue"};
console.log(tmp3 === tmp4); // false

c나 c++를 공부하며 포인터나 레퍼런스 개념에 대해 알고 있다면 친숙한 내용일 수 있다.

const value = "blue" 형식의 경우 메모리에 "blue" 값이 할당되며, const ptr = {color : "blue"}의 경우는 ptr에 메모리의 주소가 할당된다. 즉, 위 코드에서 tmp1과 tmp2는 같은 값이 할당되어 있어서 비교 시 참의 결과가 나오지만 case2의 경우에는 메모리 주소가 서로 다르기 때문에 서로 다른 값이므로 비교 시 거짓의 결과가 출력된다. 이는 String, Number, Boolean, Null, Undefined, BigInt, Symbol 등은 Primitive 즉, 원시 타입이고 객체와 같은 Object 타입 (Object, Array 등)과 다르기 때문이다.

 

이에 대해 이해하고 있다 가정한 상황에서 아래의 코드를 본다.

function App() {
	const [number, setNumber] = useState(0);
	const [isActive, setIsActive] = useState(true);
    
	const switch = isActive ? "ON" : "OFF";
    
	useEffect(()=>{
		console.log("Call useEffect()");
		// So Heavy Task..
	}, [switch]);
    
	return (
		<div>
			<input type="number" value={number} onChange={(e)=>setNumber(e.target.value)} />
		</div>
		<div>
			<p>상태: {switch.state}</p>
			<button onClick={()=>setIsActive(!isActive)}>Change State</button>
		</div>
	);
}

위 코드는 평범한 코드이다. 내가 버튼을 누르면 isActive로 인해 switch가 변경될테고 useEffect가 호출되게 된다. 그 위의 엘리먼트인 number는 아무리 변경해도 useEffect가 호출되지 않는다.

 

여기서 위 코드를 아래와 같이 변경해보자.

function App() {
	const [number, setNumber] = useState(0);
	const [isActive, setIsActive] = useState(true);
    
	const switch = {
		state : isActive ? "ON" : "OFF",
	};
    
	useEffect(()=>{
		console.log("Call useEffect()");
		// So Heavy Task..
	}, [switch]);
    
	return (
		<div>
			<input type="number" value={number} onChange={(e)=>setNumber(e.target.value)} />
		</div>
		<div>
			<p>상태: {switch.state}</p>
			<button onClick={()=>setIsActive(!isActive)}>Change State</button>
		</div>
	);
}

switch를 객체 타입으로 변경하였다. 이렇게 되면 어떤 결과가 발생할까?

switch 변수에는 주소값이 담기게 되고 이 값은 계속 변경되기 때문에 항상 useEffect 함수를 호출하는 현상이 발생한다.

이를 최적화 하기 위한 수단으로 아래와 같이 useMemo를 사용할 수 있다.

function App() {
	const [number, setNumber] = useState(0);
	const [isActive, setIsActive] = useState(true);
    
	const switch = useMemo(()=>{
		return {
			state : isActive ? "ON" : "OFF",
		};
	}, [isActive]);
    
	useEffect(()=>{
		console.log("Call useEffect()");
		// So Heavy Task..
	}, [switch]);
    
	return (
		<div>
			<input type="number" value={number} onChange={(e)=>setNumber(e.target.value)} />
		</div>
		<div>
			<p>상태: {switch.state}</p>
			<button onClick={()=>setIsActive(!isActive)}>Change State</button>
		</div>
	);
}

useMemo로 인하여 switch 값에는 이전에 저장했던 주소값이 사용되어 useEffect에서 참고하는 있는 변수의 변동이 없기 때문에 불필요한 useEffect 호출이 일어나지 않게 된다.

 

다음번 포스팅에는 useCallback에 대해 알아보도록 하자.

'Personal Posting > ReactJS' 카테고리의 다른 글

React Hook 정리 - useReducer  (0) 2023.11.06
React Hook 정리 - useCallback  (1) 2023.10.31
React Hook 정리 - useContext  (0) 2023.07.01
React Hook 정리 - useRef  (0) 2023.06.30
React Hook 정리 - useEffect  (0) 2023.06.30