2022-04-18 TIL

Fact

  • useMemo, useCallback, React.memo에 대해서 공부하기
  • 알고리즘 문제 문자열 압축, 오픈 채팅방, 행렬 테두리 회전하기 리팩토링을 했고, 내일은 거리 두기 확인하기 문제 리팩토링해야겠다.

Feelings

  • 아무래도 코딩 테스트하면 문제풀이 타임 어택이라고 생각해왔기 때문에, 항상 문제를 풀고 리팩토링할 시간이 없다고 생각하여 내가 풀었던 문제들을 다듬으면서 복기하는 작업을 안 해왔었는데, 이제 꾸준히 내가 풀었던 문제들을 다른 사람들 코드와 비교하면서 완벽하게 이해할 수 있도록 해야겠다.

Findings

useMemo

useMemo 함수에 대해서 알아보기 전에 알고리즘 시간에 자주 나오는 메모이제이션의 개념에 대해서 잠깐 짚고 넘어가면, memoization이란 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말합니다. memoization을 적절히 적용하면 중복 연산을 피할 수 있기 떄문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있습니다.

우리가 사용하는 React에서 컴포넌트는 한번이 아니라 여러번 렌더링이 발생할 수 있습니다. 예를 들면 컴포넌트의 상태가 변경될 때, 부모 컴포넌트의 상태변경이 일어날 때 자식 컴포넌트 역시 리렌더링이 발생합니다.

만약 함수형 컴포넌트의 실행이 느리다면?

아래 컴포넌트는 prop로 넘어온 x,y값을 compute 함수에 인자로 넘겨서 z값을 구한 후, 그 결과값을 div 엘리먼트로 감싸 출력해줍니다.

function MyComponent({ x, y }) {
  const z = compute(x, y)
  return <div>{z}</div>
}

만약에 compute 함수가 내부적으로 매우 복잡한 연산을 수행하기 때문에 결과값을 리턴하는데 시간이 몇초 이상 오래 걸린다면? 컴포넌트의 재 랜더링이 필요할 때 마다 이 함수가 호출 되므로 사용자는 지속적으로 UI에서 지연이 발생하는 경험을 하게 될것입니다.

렌더링이 일어날 때 마다, compute 함수의 인자로 넘어오는 x와 y 값이 항상 바뀌는 게 아니라면 굳이 compute 함수를 계속 호출할 필요가 있을까? 이러한 상황에 사용할 수 있는 것이 React에서 제공하는 useMemo hook함수 입니다. useMemo 함수는 2개의 인자를 받는데, 첫번째는 결과값을 생성해주는 팩토리 함수이고, 두번째는 기존 결과값 재활용 여부의 기준이되는 입력값 배열입니다. 예를 들어, 다음과 같이 위에서 작성한 컴포넌트를 재작성하면,

function MyComponent({ x, y }) {
  const z = useMemo(() => compute(x, y), [x, y])
  return <div>{z}</div>
}

x와 y값이 이 전에 랜더링했을 떄와 동일할 경우, 이 전 랜더링 때 저장해두었던 결과값을 재활용합니다. 하지만, x와 y값이 이 전에 레더링했을 때와 달라졌을 경우, () => compute(x,y) 함수를 호출하여 결과값을 새롭게 구해 z에 할당해줍니다.

useCallback

useCallback 또한 메모이제이션하기 위해 사용하는 hook 함수입니다. 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해줍니다.

const memoizedCallback = useCallback(함수, 배열)

예를 들어, 어떤 React 컴포넌트 함수 안에 함수가 선언이 되어 있다면 이 함수는 해당 컴포넌트가 랜더링될 때 마다 새로운 함수가 생성됩니다.

const add = () => x + y

하지만 useCallback()을 사용하면, 해당 컴포넌트가 랜더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환합니다. 즉 x또는 y값이 바뀌면 새로운 함수가 생성되어 add 변수에 할당되고, x와 y값이 동일하다면 다음 랜더링 때 이 함수를 재사용합니다.

const add = useCallback(() => x + y, [x, y])

자바스크립트 함수 동등성

useCallback() hook 함수를 언제 사용해야하는지 제대로 이해하려면 먼저 자바스크립트에서 함수 간의 동등함이 어떻게 결정되는지 알 필요가 있습니다.

const add1 = () => x + y
const add2 = () => x + y

console.log(add === add2) // false

자바스크립트에서 함수는 객체로 취급되기 때문에 메모리 주소에 의한 참조 비교가 일어나기 때문에 똑같은 코드의 함수여도 false를 반환합니다.

이러한 자바스크립트 특성은 React 컴포넌트 함수 내에서 어떤 함수를 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제로 이어질 수 있습니다.

Future Action

  • 앞으로 내가 풀었던 문제들을 복기하듯이 다시 다듬으면서 완벽하게 이해하고 넘어갈 수 있도록 해야겠다.