[React] useMemo(), useCallback(), useRef()

홍예찬·2021년 5월 2일
0
post-thumbnail

회사에서 앱을 출시하게 되면서 기존 Vue로 웹 개발을 진행함과 동시에 RN 앱 개발도 맡게 되었습니다. 기존에 React로 프로젝트도 진행해보고, 인턴십도 진행해본터라 오랜만에 리액트를 다시 시작하게 되어 기대 반 설렘반으로 앱 개발을 시작하게 되었습니다.

React로 프로젝트를 진행하면서 함수형 컴포넌트를 사용할 때 useState, useEffect 훅만을 사용했던 기억이 있습니다. useMemo나 useCallback등을 사용하지 않아도 충분히 웹 구현을 할 수 있었기 때문이었죠.

그러나 앱 개발을 시작하게 되면서 기본적인 훅뿐만 아니라 다른 훅에 대한 학습도 필요하다는 생각이 들었습니다. 사실 그 전까지 다른 훅들을 쓰지 않았던 건 쓸 필요가 없었다라기 보다는 굳이 안 배워도 되지 않을까? 라는 안일한 마음이 컸던 것 같습니다.

(등짝을 맞아도 쌉니다)

이번에는 useState(), useEffect()이외에 useMemo(), useCallback(), useRef()에 대한 내용을 포스팅해보려 합니다.

useMemo()

특정 결과값을 재사용하려 할 때 사용하는 훅입니다.
리액트 공식문서에는 useMemo()에 대해 이렇게 설명하고 있습니다.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

메모이제이션된 값을 반환합니다.
“생성(create)” 함수와 그것의 의존성 값의 배열을 전달하세요. useMemo는 의존성이 변경되었을 때에만 메모이제이션된 값만 다시 계산 할 것입니다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해 줍니다.

그렇다면 useMemo를 어떤 상황에서 사용하게 될까요?
이를 이해하기 위해서는 함수형 컴포넌트의 작동방식을 먼저 이해할 필요가 있습니다.

컴포넌트 함수는 React 앱에서 랜더링(rendering)이 일어날 때마다 호출이 됩니다. 컴포넌트 함수가 호출이 되면 그 안에 자바스크립트 로직들이 수행되고 JSX로 마크업된 UI가 리턴되죠

컴포넌트가 렌더링 되는 방식은 두 가지가 있습니다. 컴포넌트 내에 상태값의 변경이 일어나거나 부모 컴포넌트로부터 받는 상태값이 변경되는 경우가 그렇습니다.

function childComponent({x, y}) {
  //...
  const z = computeExpensiveValue(x,y)
  return <div>{z}</div>
}

만약 함수명처럼 computeExpensiveValue 가 엄청나게 복잡한 연산을 수항하기 때문에 시간이 몇 초 이상 걸리게 된다면 어떨까요? 렌더링이 발생할 때마다 사용자는 빈 화면을 계속하게 마주하게 될겁니다.

(사용자 표정 = 내 표정)

랜더링이 발생할 때마다, props로 넘어오는 x와 y 값이 이전 렌더링과 현재 렌더링 간에 x, y값이 동일하다면 굳이 computeExpensiveValue 함수를 계속 호출할 필요가 없습니다. 함수 호출 대신, 기존 메모리에 저장해둔 z 값을 사용하는 것이죠.

따라서 위의 함수를 useMemo를 활용해 이렇게 바꾸면 됩니다.

function childComponent({x, y}) {
  //...
  const z = useMemo(()=> computeExpensiveVaue(x,y),[x,y])
  return <div>{z}</div>
}

어디서 많이 본 함수 모습이지 않나요? useEffect()와 상당히 비슷한 구조입니다. useEffect의 경우에도 두 번째 인자 배열에 상태값을 넣고 상태값이 변화될 때마다 업데이트가 되는 로직이죠!

그러나 리액트 공식문서를 보면 useMemo()에 대해 약간은 미온적인(?) 태도를 견지하는 것 같습니다. (그래도 알아두면 렌더링 최적화를 위해 요긴하게 쓸 수 있겠죠? 뭐든 모르는 것보다 투머치해도 아는 게 낫다는 주의입니다..!)

useMemo는 성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요. 가까운 미래에 React에서는, 이전 메모이제이션된 값들의 일부를 “잊어버리고” 다음 렌더링 시에 그것들을 재계산하는 방향을 택할지도 모르겠습니다. 예를 들면, 오프스크린 컴포넌트의 메모리를 해제하는 등이 있을 수 있습니다. useMemo를 사용하지 않고도 동작할 수 있도록 코드를 작성하고 그것을 추가하여 성능을 최적화하세요.

useCallback()

특정 함수를 재사용할 때 사용하는 훅으로. useMemo() 와 쓰임새가 비슷합니다.(조금 더 엄연하게 보자면 결과값을 재사용하냐, 특정 함수를 재사용하냐의 차이로, 역할만 다르고 동일한 기능을 수행하는 훅입니다.)

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

⚠️주의할 점!⚠️

함수 안에서 사용하는 상태나 props 가 있다면 꼭, 두 번째 인자 배열 안에 포함시켜야 합니다! 만약 값을 넣지 않게 된다면, 함수 내에서 해당 값들을 참조할때 가장 최신 값을 참조 할 것이라고 보장 할 수 없습니다.

useRef()

자바스크립트에서 특정 DOM에 접근할 때, querySelectorgetElementsById와 같은 셀렉터를 통해 접근하게 됩니다.
그러나 리액트 는 Vanilla JS를 사용하지 않고도 특정 DOM에 접근하는 훅을 제공하고 있습니다.
useRef() 훅에 관해서는 리액트 공식문서에서 자세히 설명하고 있습니다.

useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다. 반환된 객체는 컴포넌트의 전 생애주기를 통해 유지될 것입니다.
일반적인 유스케이스는 자식에게 명령적으로 접근하는 경우입니다.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

이상으로 useState(), useEffect() 훅 이외에 리액트에서 제공하는 여러 훅들을 살펴봤습니다!

profile
내실 있는 프론트엔드 개발자가 되기 위해 오늘도 최선을 다하고 있습니다.

0개의 댓글