본문 바로가기
WEB/깊게 공부하기

[React] Optimization, useMemo, useCallback, memo - (1)

by IT황구 2022. 3. 7.
728x90
반응형

반갑습니다 ^^ 

 

최근에는 chrome profiler 사용법을 익히고, 렌더링에 얼마나 걸리나 이런것들을 보는 재미로 지내고 있습니다.

Profiler 보는법은 다음번에 포스팅을 해보도록 하겠습니다.

 

서론이 좀 길 수 있지만, 참고 읽어보시면 분명 도움이 될 것이라고 확신합니다.

 

 

일반적인 사람들의 오해

렌더링이 적으면 무조건 속도가 빠르다 -> React.memo 등 최적화를 위한 HOC나 hook들을 사용합니다.

하지만 이 말은 틀렸다고 생각합니다.

 

잘못된 케이스의 경우를 생각해보겠습니다.

- deps가 어떻게 비교되어 작동되는지 모른다면, 무조건 렌더링이 되고, 비교함수도 호출 되므로 더 느려집니다.

- 렌더링을 방지하려고 deps에서 비교하는 시간이 더 들 수 있습니다.

 

어떻게 사용해야 할까?

이미 React 자체로도 충분히 불필요한 rendering을 잘 막아주고 있습니다.

우리가 페이스북이 만든 diffing 알고리즘보다 더 잘 예상해서, 불필요한 렌더링을 막아낼 확률은 낮습니다.

 

Rendering 방지의 목적으로만 사용한다면 더 처참한 performance를 확인하게 됩니다.

성능을 정확하게 정량화 할 수 없고, 이게 맞나? 생각이 들면 하지 않는것이 더 낫습니다.

 

즉, 우리는 이런 hook이나 HOC를 성능 개선의 도구 로만 사용해야 합니다.

 

시작에 앞서 DOCS에 있는 shouldComponentUpdate, React.memo에 대한 공통적인 설명은 다음과 같습니다.

모르면 쓰지 마라 라고 하는것 같습니다..

메모리를 조금 더 써서 이전의 값들을 기억해뒀다가, 새로 렌더링을 하지 않는것이 올바른 접근이라고 생각합니다.

 

언제 써야할까?

변경된 DOM 노드만 업데이트 하더라도, re-render에는 시간이 여전히 걸립니다.

대부분의 경우에는 체감이 어렵지만, 속도저하가 눈에 띄는 경우 사용하는것이 좋습니다.

  • 렌더링 되는 컴포넌트의 시간이 오래 걸릴경우
  • useMemo를 사용한다면, 실행 함수의 시간이 오래 걸리는 경우.
  • 무거운 작업을 기억할때 사용하자

모든 경우의 수를 다 쓸수는 없습니다. 기능들에 대한 올바른 이해를 바탕으로 적절하게 사용하는것이 옳습니다.

굉장히 무책임한 말이지만, 포스팅을 다 읽으신다면 분명 구분할 수 있을 것입니다.

 

이해에 도움을 주는 deps가 들어가는 hook들의 작동 원리

useMemo, useCallback, useEffect 등 deps가 들어가는 곳에는 어떻게 deps를 비교할까요?

 

useMemo는 값을 어떻게 기억할까?

  • globalState로 Object에 값들을 계속 기억해 줘야 합니다.
  • useMemo는 memoizedState를 사용하고, Array 형태입니다.

hook object 안에 deps와 값 정보들을 담고 있습니다.

  • deps가 같은지는 어떤 함수로 확인할까요?
  • areHookInputEquals를 사용합니다.

  • is는 Object.is의 polyfill 입니다.
  • 핵심은 Object.is로 비교한다는 것입니다. (원시타입은 값만, 참조 타입은 reference 비교)

결론 : deps에 너무 많은 값을 넣거나, 참조타입이면 고민을 해봐야 합니다.

return false가 되어서, 계속 렌더링이 일어날 수 있습니다.

 

useMemo

- 함수의 결과값을 기억하기 위해서 사용하는 hook 입니다.

  • deps를 비교해서 변경됐다면 함수를 재 호출, 그렇지 않다면 기존의 값을 재사용 합니다.
  • deps를 확인하려면 prev,next를 비교하는 추가 작업이 들게 됩니다.

- 여기서의 핵심은 computeExpensiveValue 함수 입니다.

  • 잘 변하지 않는 고비용 함수 에서는 이게 굉장히 효율적입니다.

//App.js

import React, { useState, useMemo } from "react";

const expensiveCalculation = (num) => {
	console.log("Calculating...");
	for (let i = 0; i < 1000000000; i++) {
		num += 1;
	}
	return num;
};

const App = () => {
	const [count, setCount] = useState(0);
	const [todos, setTodos] = useState([]);
//const calculation = useMemo(() => expensiveCalculation(count), [count]);
	const calculation = expensiveCalculation(count);
	const increment = () => {
		setCount((c) => c + 1);
	};
	const addTodo = () => {
		setTodos((t) => [...t, "New Todo"]);
	};

	return (
		<div>
			<div>
				<h2>My Todos</h2>
				{todos.map((todo, index) => {
					return <p key={index}>{todo}</p>;
				})}
				<button onClick={addTodo}>Add Todo</button>
			</div>
			<hr />
			<div>
				Count: {count}
				<button onClick={increment}>+</button>
				<h2>Expensive Calculation</h2>
				{calculation}
			</div>
		</div>
	);
};
export default App;

useMemo를 사용 했을때와, 사용하지 않았을때 속도를 비교해보세요.

 

"Add Todo"버튼을 눌렀을때, useMemo는 count가 변하지 않았으므로, 기존의 값을 재사용 합니다.

따라서 멈추지 않습니다. 하지만 useMemo를 사용하지 Add 버튼을 클릭할때마다 느려지는것을 볼 수 있습니다.

 

 

쓰다보니 길어져서.. 다음번에는 React.memo, useCallback으로 이어서 포스팅 하겠습니다.

 

React.memo와 useCallback은 궁합이 좋습니다. 또한 memo는 주어진 props를 참조만 보고 대충대충 넘기지 않습니다. 마치 패자부활전 처럼 한번의 기회가 더 있는데.. 다음 시간에 봅시다!

 

감사합니다

728x90
반응형