useMemo
2023-09-04
ReactHooks
Table of Contents
useMemo가 나오게 된 배경
- 렌더링 시 계산 비용이 많이 들어가는 계산에 대해서 값을 재사용하고, 렌더링 시 실행을 방지해 성능을 최적화 하기 위해서 나온 Hook.
1. 프로젝트 세팅 React + TS + Vite
$ npm init vite@latest React-UseMemo -- --template react-ts
$ cd React-UseMemo
$ npm i
/src/App.tsx
import "./App.css";
import { useState } from "react";
function App() {
// useState의 set 함수 호출로 updating 트리거
const [render, setRender] = useState(false);
// updating 과정마다 반복문 계속 실행.
const value = calculate();
function renderHandler(event: React.MouseEvent) {
event.preventDefault();
setRender(!render);
}
return (
<div>
{value}
<button onClick={renderHandler}>render!</button>
</div>
);
}
function calculate() {
for (let i = 0; i < 200; i++) {
console.log(i);
}
return 10;
}
export default App;
- 반복문이 컴포넌트가 rendering 될 때마다 실행되고 있습니다. 매번 200번의 반복문이 돌아가고 있습니다.
2. useMemo 사용해보기
useMemo의 구조
const cachedValue = useMemo(calculateValue, dependencies)
- useMemo는 첫번째 파라미터로 저장할 값을 받고, 두번째 파라미터로 의존성 배열을 받습니다. 이 의존성 배열 안에 값이 바뀐다면 값을 다시 계산하고 할당합니다.
- 의존성 배열에 빈값이 할당된다면, 첫 마운트 시에 계산된 값을 저장하고, 다시 실행하지 않습니다.
- 의존성 배열에 값이 할당된다면, 첫 마운트 혹은 그 값이 변경되었을때 다시 할당합니다.
의존성 배열에 값이 없을 시
import "./App.css";
import { useState, useMemo } from "react";
function App() {
// useState의 set 함수 호출로 updating 트리거
const [render, setRender] = useState(false);
// useMemo 추가로 반복문이 실행되지 않음
const value = useMemo(() => {
return calculate();
}, []);
// const value = calculate();
function renderHandler(event: React.MouseEvent) {
event.preventDefault();
setRender(!render);
}
return (
<div>
{value}
<button onClick={renderHandler}>render! {`${render}`}</button>
</div>
);
}
function calculate() {
for (let i = 0; i < 200; i++) {
console.log(i);
}
return 10;
}
export default App;
- 첫 마운트 시에만 계산하고, 저장합니다.
의존성 배열에 값이 있을 시
import "./App.css";
import { useState, useMemo } from "react";
function App() {
// useState의 set 함수 호출로 updating 트리거
const [render, setRender] = useState(false);
// render를 추가함에 따라 render로 useMemo가 초기화됨
const value = useMemo(() => {
return calculate();
}, [render]);
// const value = calculate();
function renderHandler(event: React.MouseEvent) {
event.preventDefault();
setRender(!render);
}
return (
<div>
{value}
<button onClick={renderHandler}>render! {`${render}`}</button>
</div>
);
}
function calculate() {
for (let i = 0; i < 200; i++) {
console.log(i);
}
return 10;
}
export default App;
- 첫 마운트 시와 render의 값이 바뀔 시 계산하고, 저장합니다.
참조형 데이터 타입의 주소가 변하는 경우
- 함수형 컴포넌트가 re-rendering되는 경우 참조형 타입의 경우 주소 값이 재할당이 되어, 참조형 데이터타입의 값이 실제로 바뀌지 않았는데도 바뀌었다고 인식하는 경우가 있습니다.
import "./App.css";
import { useState, useMemo, useEffect } from "react";
function App() {
// useState의 set 함수 호출로 updating 트리거
const [render, setRender] = useState(false);
const [number, setNumber] = useState(0);
const updating = {
render: render ? true : false,
};
// updating 과정마다 반복문 계속 실행.
const value = useMemo(() => {
return calculate();
}, [render]);
// updating 구독, 리렌더링 시 계속 바뀌게 된다.
useEffect(() => {
console.log("updating ::: " + updating.render);
}, [updating]);
// const value = calculate();
function renderHandler(event: React.MouseEvent) {
event.preventDefault();
setRender(!render);
}
// number를 증가하게 하는 handler
function onClickHanlder(event: React.MouseEvent) {
event.preventDefault();
setNumber((prev) => prev + 1);
}
return (
<div>
{value}
<button onClick={renderHandler}>render! {`${render}`}</button>
<button onClick={onClickHanlder}>Number! {`${number}`}</button>
</div>
);
}
function calculate() {
for (let i = 0; i < 200; i++) {
console.log(i);
}
return 10;
}
export default App;
- number state가 증가하게 되는 버튼을 누르면 useEffect는 updating 변수가 바뀌는 것을 감지하고 console.log를 출력하고 있습니다. 전혀 관계 없고, 동작하지 않은 변수가 다시 메모리 주소만 바뀌어 할당되고 있습니다.
const updating = useMemo(() => {
return {
render: render ? true : false,
};
}, [render]);
- updating 상수를 수정해보겠습니다. useMemo로 감싸고, render state를 의존성 배열에 넣겠습니다. 어떤가요? 실행되지 않습니다.
useMemo 주의사항
- useMemo 를 남용하지 말고, 계산 비용이 높은 작업에만 사용해야 합니다.
- 종속성 배열을 명확히 지정하여 어떤 값이 변경될 때 재계산할 지를 정확히 제어해야 합니다.
- useMemo 내에서 생성된 값은 불변성을 유지하고, 최신 데이터를 가져와서 사용하는 것이 중요합니다.