React 공부/기초

useMemo(), useCallback(), useRef()에 대하여

책다니엘 2024. 1. 16. 20:44
useMemo()란?

 

useMemo() 훅은 Memoized value를 리턴하는 훅이다.

 

Memoized value를 이해하기 위해서는 Memoization을 먼저 알아야 하는데, 이는 최적화를 위해서 사용하는 개념으로, 비용이 높은(연산량이 많이 드는) 함수의 호출 결과를 저장해 두었다가, 같은 입력 값으로 함수를 호출하면 새롭게 함수를 호출하지 않고 이전에 저장해놨던 호출 결과를 바로 반환하는 것이다.(불필요한 연산을 적게 사용하기 위해)

 

즉 Memoized value는 "메모된 value"값이라 이해하면 편하다!

 

그렇다면 useMemo()훅은 어떤 방식으로 사용되는 걸까?

const memoizedValue = useMemo(
    ( ) => {
        // 연산량이 높은 작업을 수행하여 결과를 반
        return computeExpensiveValue(의존성 변수 1, 의존성 변수 2);
    },
    [의존성 변수 1, 의존성 변수 2]
);

 

useMemo() 훅은 패러미터로 memoizedValue를 생성하는 create 함수와 의존성 배열을 받는다. 메모이제이션의 개념처럼, 의존성 배열에 들어있는 함수가 변했을 때만 새로 create함수를 노출하여 결과값을 반환하며, 그렇지 않은 경우에는 기존 함수의 결과값을 반환한다.

 

이러한 방식을 통해 컴포넌트가 다시 렌더링될 때마다 연산량이 높은 작업을 반복하지 않도록 할 수 있다.

 

 

 

useMemo()의 사용법은?

 

useMemo() 훅을 사용할 때 주의해야 할 점은, useMemo로 전달된 함수는 렌더링이 '일어나는 동안' 실행된다는 것이다.

< 렌더링이 일어나는 동안 실행되어서는 안 되는 작업들은 useMemo()의 함수에 절대로 넣지 말 것!!! >

ex) useEffect()훅에서 실행되어야 할 side effect 같은 경우, 서버에서 데이터를 받아 오거나 수동으로 DOM을 변경하는 작업 등

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

 

 

그리고 또한 이 코드와 같이 의존성 배열을 넣지 않을 경우, 새로 렌더링이 일어날 때마다 해당 코드가 매번 실행된다.

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

 

 

의존성 배열이 빈 배열일 경우, 컴포넌트 마운트 시에만 호출된다.(마운트 시에 딱 한 번만 값을 계산할 필요가 있다면 이런 식으로 코드를 짜면 된다.) 하지만, 대부분의 경우에는 useMemo() 훅에 의존성 배열의 변수들을 넣고, 해당 변수들의 값이 변동될 때 새로 값을 계산해야 할 경우 사용한다.

 

 

useCallback() Hook은 이전에 나온 useMemo() Hook과 비슷한 역할인데, 차이점은 값이 아닌 함수를 반환한다는 점이다.

쉽게 말하면, 컴포넌트가 렌더링될 때마다 매번 함수를 새로 정의하는 것이 아니라, 의존성 배열의 값이 바뀐 경우에만 함수를 새로 정의해서 리턴해주는 것이다.

 

const memoizedCallback = useCallback( 
    ( ) => {   
        doSomething(의존성 변수 1, 의존성 변수 2); 
        },
    [의존성 변수 1, 의존성 변수 2]
);

 

 

useCallback() 훅은 useMemo() 훅과 마찬가지로 함수와 의존성 배열을 패러미터로 받는다. useCallback() 훅에서는 파라미터로 받는 이 함수를 callback이라고 부른다. 그리고 의존성 배열에 있는 변수 중 하나라도 변경되면 메모이제이션된 콜백 함수를 반환한다. 의존성 배열에 따라 메모이즈드 값을 반환한다는 점에서는 useMemo()훅과 동일하다. 따라서,

 

useCallback(함수, 의존성 배열);
useMemo( ( ) => 함수, 의존성 배열); 

 

위 두 줄의 코드는 동일한 역할을 한다고 볼 수 있다.

 

 

useRef() 훅이란?

 

useRef() 훅은 레퍼런스를 정의하기 위한 훅이다.

 

여기서 레퍼런스란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.

 

 

useRef()훅은 이 레퍼런스 객체를 반환하는 역할을 한다.

 

 

refObject.current

 

위와 같이 레퍼런스 객체에는 current란 속성이 있는데, 이것은 현재 레퍼런스하고 있는 엘리먼트를 의미한다. 

 

 

useRef()는 다음과 같이 사용된다.

 

const refContainer = useRef( 초깃값 );

 

패러미터에 초깃값을 넣으면, 해당 초깃값으로 초기화된 레퍼런스 객체를 반환한다. 이렇게 반환된 레퍼런스 객체는 해당 컴포넌트의 라이프타임 사이클 전체에 걸쳐(componentWillUnmount 시까지) 유지된다. 쉽게 말해, useRef() 훅은 변경 가능한 current라는 속성을 가진 하나의 상자라고 보면 된다고 한다.

 

function TextInputWithFocusButton(props) {
  const inputElem = useRef(null);
  const onButtonClick = () => {
    // `current`는 마운트된 input element를 가리킴
    inputElem.current.focus();
  };

  return (
    <>
      <input ref={inputElem} type="text">
        <button onClick={onButtonClick}>Focus the Input</button>
      </input>
    </>
  );
}

 

위 코드는 useRef() Hook을 사용하여 버튼 클릭 시 input에 포커스를 하도록 하는 코드이다.

 

<div> ref = {myRef} </div>

 

 

리액트에서는 위와 같이 코드를 작성하면, 노드가 변경될 때마다 myRef의 current속성에 해당되는 DOM 노드를 저장한다. DOM의 ref속성과 기능은 비슷하지만, useRef() 훅은 클래스의 instance 필드를 사용하는 것과 유사하게, 다양한 변수를 저장할 수 있다는 장점이 있다. 이런 것이 가능한 이유는 useRef()훅은 일반적인 자바스크립트 객체를 리턴하기 때문이다. 

 

그렇다면 사용자가 직접 current 속성이 포함된 자바스크립트 객체를 만들어서 쓰면 어떨까?

 

물론 그런 방식도 가능은 하지만, useRef() Hook을 사용하는 것과 직접 current 속성이 포함된 객체를 만들어 쓰는 것의 차이점은, useRef() 훅은 매번 렌더링될 때마다 항상 같은 레퍼런스 객체를 반환한다는 것이다.

 

기억해야 할 점이 있는데, useRef() 훅은 내부의 데이터가 변경되었을 때 별도로 알리지 않는다. 

따라서, current 속성을 변경한다고 해서 재렌더링하지 않는다.

 

이로 인해, ref에 돕노드가 연결되거나 분리되었을 때 어떤 코드를 실행하고 싶다면, Callback ref를 사용해야 한다. 

 

 

Callback ref란?

 

 

리액트는 ref가 다른 노드에 연결될 때마다 callback을 호출한다.

 

function MeasureExample(props) {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback((node) => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);
  
  return (
    <>
      <h1 ref={measuredRef}>안녕, 리액트</h1>
      <h2> 위 헤더의 높이는 {Math.round(height)}px입니다. </h2>
    </>
  );
}

 

이 코드에는 레퍼런스를 위해서 useRef() hook을 사용하지 않고, useCallback hook을 사용하는 callback ref을 사용했다. useRef hook을 사용하면 레퍼런스 객체가 current 속성이 변경되었는지 알려주지 않기 때문이다. 하지만 callback ref 방식을 사용하게 되면, 자식 컴포넌트가 변경되었을 때 알림을 받을 수 있어, 이를 통해 다른 정보들을 업데이트할 수 있다.

 

출처: 인프런 - 소플의 처음 만난 리액트

(https://www.inflearn.com/course/lecture?courseSlug=%EC%B2%98%EC%9D%8C-%EB%A7%8C%EB%82%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8&unitId=113223)

'React 공부 > 기초' 카테고리의 다른 글

React, Next.js 주요 개념  (6) 2024.10.03
Hook의 규칙과 custom Hook 만들기  (0) 2024.01.17
Hooks의 개념과 useState(), useEffect()  (2) 2024.01.14