자주 사용되는 값을 받아오기 위해 반복적으로 계산을 해야한다면, 이전에 이미 계산한 값을 캐싱하여 해당 값이 필요할 때마다 반복적인 계산이 아닌, 이를 메모리에서 꺼내 사용하는 최적화 기법.
인라인 콜백과 그것의 의존성 값의 배열을 전달하세요.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
비싼 비용의 계산을 해야하는 경우
const calc = (number) => {
for(let i = 0; i < 9999999; i++) {} // loading
return number + 10000;
}
export default function App() {
const [number, setNumber] = useState(1);
// 비싼 비용이 드는 계산을 매 렌더마다 실행한다.
const sum = calc(number);
// useMemo를 통해 메모이제이션한 값을 사용하면 렌더 지연을 막을 수 있다.
const sum = useMemo(() => calc(number), [number]);
return (
<div className="App">
<input type="number" value={number} onChange={(e) => setNumber(e.target.value)} />
<p>+10000 = {sum}</p>
</div>
);
}
참조 타입인 객체의 변수를 사용하는 경우
객체를 변수에 저장할 경우 재렌더될 때마다 해당 객체의 참조값은 계속 바뀌게 된다.
아래의 예제에서 number가 바뀔 때마다 useEffect가 호출된다. state인 number가 바뀌면서 재렌더될 때마다 location은 새로운 참조값을 가지게 된다. 때문에 location이 계속 변경되어 useEffect가 재렌더마다 실행되는 것이다. 이를 막기 위해서 location을 useMemoe로 감싸거나 useEffect의 의존성을 location.country로 지정해야 한다.
export default function App() {
const [number, setNember] = useState(0);
const [isKorea, setIsKorea] = useState(true);
// 재렌더마다 새로운 참조값이 할당된다.
const location = {
country: isKorea ? "한국" : "외국"
};
// 1. useMemo를 통해 기존 값을 유지
const location = useMemo(() => {
return {
country: isKorea ? "한국" : "외국"
}
}, [isKorea]);
// 재렌더마다 실행
useEffect(() => {
console.log("location 변경");
}, [location]);
// 2. 의존성을 바꾸기
useEffect(() => {
console.log("location 변경");
}, [location.country]);
return (
<div className="App">
<input
type="number"
vlaue={number}
onChange={(e) => setNember(e.target.value)}
/>
<p>나라: {location.country}</p>
<button onClick={(e) => setIsKorea(!isKorea)}>비행기 타자</button>
</div>
);
}
참조 타입인 객체의 값을 자식에게 props로 넘겨줘야하는 경우
앞서 말했듯이 객체를 할당한 변수는 재렌더시마다 참조값이 바뀐다. 때문에 객체 값을 props로 자식에게 넘겨줄 때 객체 값이 그대로이더라도 참조값이 바뀌므로 자식 컴포넌트가 재렌더되게 된다. 이를 막기 위해서 useMemo로 객체 값을 메모이제이션 해야한다.
// 부모 컴포넌트
export default function App() {
const [age, setAge] = useState(10);
const name = useMemo(() => {
return {
first: "dony",
last: "jin"
}
}, []);
return (
<div className="App">
<h1>부모 컴포넌트</h1>
<input type="number" onChange={(e) => setAge(e.target.value)} />
<User name={name} age={age} />
</div>
);
}
// 자식 컴포넌트
export default function User({ name, age }) {
useEffect(() => {
console.log("render children");
}, [name]);
return (
<div>
<h2>자식 컴포넌트</h2>
<p>이름: {name.first}</p>
<p>나이: {age}살</p>
</div>
);
}
useMemo
는 성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요. 가까운 미래에 React에서는, 이전 메모이제이션된 값들의 일부를 “잊어버리고” 다음 렌더링 시에 그것들을 재계산하는 방향을 택할지도 모르겠습니다. 예를 들면, 오프스크린 컴포넌트의 메모리를 해제하는 등이 있을 수 있습니다. useMemo
를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요.