한 4달전쯤 크래프톤 정글에서 다이나믹 프로그래밍을 공부하면서 메모이제이션을 처음 접했는데요,
앞서 이미 계산한 값을 반복해서 계산하는 대신, 배열 같은 자료구조에 저장해 두고 필요할 때 다시 써먹는 방법이였죠.
리액트에서도 비슷하게 메모이제이션을 활용 가능합니다. 값/함수/컴포넌트를 메모리에 저장해두어, 불필요한 연산과 리렌더링을 방지하는 것이죠
useMemo
- 값 최적화const memoizedValue = useMemo(() => {
return 계산할값;
}, [의존성]);
useMemo
는 값의 불필요한 재계산을 방지하는Hook입니다. 처음 계산된 결과를 메모리에 저장하고, 이후 컴포넌트가 리렌더링되더라도 의존성 배열의 값이 바뀌지 않으면 재계산 없이 저장값을 그대로 반환합니다.
다만, 의존성 배열에 포함된 값이 변경되면 해당 계산을 다시 수행합니다.
import { useState, useMemo } from "react";
function Example() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
const double = useMemo(() => {
return count * 2;
}, [count]);
return (
<div>
<p>count: {count}</p>
<p>double: {double}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="텍스트 입력"
/>
</div>
);
}
위 코드 같은 경우 텍스트를 <input>
에 입력해 text
state가 변경되면 Example
컴포넌트가 리렌더링됩니다. 하지만 double
이 재계산되지는 않습니다. 변수 count
는 그대로기 때문입니다.
React.memo
- 컴포넌트 최적화export default React.memo(컴포넌트명);
React.memo
는 컴포넌트의 불필요한 재렌더링을 방지하며, 컴포넌트에 감싸서 사용합니다. 처음 렌더링할 때 결과를 메모리에 저장하고, 이후 컴포넌트가 리렌더링되더라도 props
가 변하지 않으면 저장된 렌더링 결과를 재사용합니다.
다만, props
가 변경되면 컴포넌트가 재렌더링됩니다.
const Child = React.memo(({ value }) => {
return <div>{value}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<Child value="고정된 값" />
<button onClick={() => setCount(count + 1)}>+</button>
</>
);
}
위 코드 같은 경우 button
을 클릭하면 count
가 변경되어 Parent
가 리렌더링되더라도, React.memo
로 감싼 Child
는 리렌더링되지 않습니다. 전달되는 prop
인 value
가 그대로기 때문입니다.
useCallback
- 함수 최적화const memoizedFunc = useCallback(콜백함수, [의존성]);
useCallback
은 함수의 불필요한 재생성을 방지하는 훅입니다. 함수도 JS 객체이기 때문에, 함수를 포함하는 컴포넌트가 리렌더링될 시 함수 내부 코드는 동일해도 별도의 객체로 재생성됩니다. 이러한 현상을 방지하기 위해서 useCallback
을 사용하며, 이후 컴포넌트가 리렌더링되더라도 의존성 배열의 값이 바뀌지 않으면 함수를 다시 생성하지 않습니다.
다만, 의존성 배열에 포핟된 값이 변경되면 해당 함수를 재생성합니다.
참고로 useCallback(콜백함수, 의존성배열)
은 useMemo(() => 콜백함수, 의존성배열)
과 동일한 동작을 합니다. 하지만 깔끔한 코드를 위해선 useCallback
을 쓰는 게 더 좋겠죠.
import { useState, useCallback } from "react";
function Example() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return <button onClick={increment}>+1</button>;
}
위 코드의 경우, button
을 눌러 count
state가 변경되어 Example
컴포넌트가 다시 렌더링되어도, increment
함수가 다시 생성되지 않습니다. 의존성 배열이 비어 있어서, 처음 렌더링될 때 빼고는 절대로 함수가 다시 생성되지 않아요.
우선 프로젝트 기능을 구현하고, 최적화는 맨 마지막에 하는 것이 권장됩니다. 이런 최적화 방법을 처음부터 적용하면, 추가로 기능을 구현할 시 코드가 복잡해져서 유지보수가 어려워질 수 있습니다.
그리고 꼭 모든 컴포넌트/값/함수에 최적화를 하기보단, 필수적인 코드에만 하는 게 권장됩니다. 메모이제이션 시 의존성 배열의 값이나 props가 변경되었는지 확인해야 하므로 오버헤드가 발생하기 때문입니다.
연휴에 안 쉬고 복습이 올라온 거 ㄷㄷ 그냥 송상록을 국회로