반갑습니다 ^^
최근에는 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 형태입니다.
- 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를 참조만 보고 대충대충 넘기지 않습니다. 마치 패자부활전 처럼 한번의 기회가 더 있는데.. 다음 시간에 봅시다!
감사합니다

'WEB > 깊게 공부하기' 카테고리의 다른 글
[JS] iterable, iterator 살펴보기 - Generator 1 (0) | 2023.10.16 |
---|---|
React가 이벤트를 관리하는 방식 - 3 (완) (2) | 2023.06.19 |
React가 이벤트를 관리하는 방식 - 2 (0) | 2023.06.18 |
React가 이벤트를 관리하는 방식 - 1 (2) | 2023.06.15 |
[React] Optimization, useMemo, useCallback, memo - (2) (0) | 2022.03.09 |