Memoization
Memoization란?
Memoization은 특정한 값을 저장해뒀다가, 이후에 해당 값이 필요할 때 새롭게 계산해서 사용하는게 아니라 저장해둔 값을 활용하는 테크닉.
useMemo
useMemo는 리액트에서 값을 memoization 할 수 있도록 해주는 함수.
// useMemo(callbackFunction, deps]
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo는 두가지 인자를 받는다. 첫번째 인자는 콜백함수이며 이 함수에서 리턴하는 값이 메모된다. 두번째 인자는 의존성 배열이다.
메모이제이션을 할 때 주의해야 할 점은 만약 새로운 값을 만들어서 사용해야 하는 상황임에도 불구하고 이전의 결과를 그대로 활용해버리면 버그가 발생할 수 있다.
위의 예시에서 a, b 라는 두가지 변수를 이용해서 메모이제이션 하기 위한 값을 계산하고 있다. 그런데 만약 a, b 라는 값이 변경되었는데 이전의 값을 그대로 활용해버리면 의도한 결과와 다른 결과가 나오게 된다.
이런 상황을 방지하기 위해서 useMemo에서는 의존성 배열을 인자로 받아, 의존성 배열에 있는 값 중 하나라도 이전 렌더링과 비교했을 때 변경되었다면 메모된 값을 활용하는 것이 아니라 새로운 값을 다시 계산합니다.
useCallback
useCallback은 useMemo를 조금 더 편리하게 사용할 수 있도록 만든 버전이다.
일반적인 값들은 useMemo를 통해서 메모하기 편하지만, 함수의 경우 useMemo를 사용해서 메모하게 되면 콜백함수에서 또다른 함수를 리턴하는 형태가 된다. (이는 동작상에는 아무런 이상이 없지만 코드 스타일에 따라 문법적으로 다소 보기가 불편해지는 단점이 있다.)
따라서 이런 동작을 간소화한 useCallback이란 함수를 만들어서 제공해주고 있다.
const memorizedFunction = useMemo(() => () => console.log("Hello World"), []);
const memorizedFunction = useCallback(() => console.log("Hello World"), []);
언제 memoization을 해야 할까?
명확한 목적없이 무작정 메모이제이션을 사용하는 것은 오히려 비효율적이다.
만약 새로운 값을 만드는 과정이 복잡하다면 메모이제이션을 사용하는 것이 더 효율적일 수 있지만 새로운 값을 만드는 과정이 복잡하지 않다면 메모이제이션을 사용하는 것은 오히려 비용이 더 많이 들수도 있기때문이다.
리액트에서 메모이제이션이 필요하다고 판단할 수 있는 요인은 아래 두가지다.
- 새로운 값을 만드는 연산이 복잡하다.
- 함수 컴포넌트의 이전 호출과, 다음 호출 간 사용하는 값의 동일성을 보장하고 싶다.
1번의 경우에는 만약 10000개의 요소를 가진 배열이 있다고 생각하면 이 배열을 매번 생성하는 것 보다는 메모해서 활용하는 것이 효율적일 것이다.
2번의 경우에는 함수 컴포넌트의 호출 간 값들의 동일성을 보장하기 위해서다. 그렇다면 왜 동일성을 보장해야 할까? 바로 React.memo 와 연동해서 사용하기 위해서다.
memo의 잘못된 활용 예시중 하나는 props로 전달되는 객체의 동일성이 보장되지 않아 실제 객체의 내용은 똑같아도 shallow compare를 통해서 다른 객체라고 판단되어서 매번 리렌더링이 실행되는 상황이다.
이런 상황에서 전달되는 객체의 동일성을 보장하기 위해서 메모이제이션을 활용할 수 있다.
메모이제이션 된 객체는 새롭게 만들어진 것이 아니라 이전의 객체를 그대로 활용하는 것이기에 shallow compare에서 동일함을 보장받을 수 있다.
https://codesandbox.io/p/sandbox/react-memo-memoization-3zydw6?file=%2Fsrc%2Findex.js
codesandbox.io
>> react.memo + memoization 작동을 확인할 수 있는 사이트