본문 바로가기
안녕하세요 :) FE 개발자 윤지홍입니다.
저는 리액트를 주로 사용해요.
UX/UI디자인에도 관심이 있어요.
Javascript React NextJs NodeJs Flutter HTML CSS PHP
👋
React useRef 사용하기
jiiii-hong | React | 2022년 08월 16일

React useRef 사용하기

React useRef 사용하기

jiiii-hong React 2022년 08월 16일

useRef란?

useRef란 원하는 특정 DOM을 직접 선택해서 컨트롤 할 수 있게 해주는 Hook이다.
예를 들면 엘리먼트의 크기를 가져오거나 스타일 변경, 포커스 등의 작업을 해야할 때 useRef를 사용하면 된다.

사용법

기본적인 사용법은 useRef로 객체를 생성하고 원하는 DOM에 ref객체를 넣어주면
ref.current가 해당 DOM을 가르키게되고 current를 사용해 DOM을 직접 조작 하면된다.

import { useRef } from 'react';

const App = () => {
  const ref = useRef();

  return <input type="text" name="keyword" ref={ref} />;
};

예제위에서 만든 컴포넌트를 사용해서 button 클릭 시 input 값이 초기화 되게 만들어보자.

import { useRef } from 'react';

const App = () => {
  const ref = useRef();

  const onClickButton = () => {
    ref.current.value = '';
  };

  return (
    <div>
      <input type="text" name="keyword" ref={ref} />
      <button onClick={onClickButton}>input 초기화</button>
    </div>
  );
};

useRef로 Node 추적

ref는 값 변경사항에 대해 알려주지 않기 때문에 callback Ref를 사용하면 된다. (불필요한 리렌더링이 발생하지 않는다.)

const App = () => {
  const [node, setNode] = useState(null);

  const ref = useCallback(_node => {
    if (_node) {
      setNode(_node);
    }
  }, []);

  return <div className="App" ref={ref} />;
};

useCallback을 사용하는 이유는 element에 ref을 넘겨줄때 useCallback을 사용하게되면 콜백 함수는 처음 한번만 만들어진다.

이때 만들어진 함수를 A라고 하자. 그리고 A가 실행되어 상태값이 변경되기 때문에 리렌더링이 일어나는데 이때 useCallback을 사용하지 않으면 함수가 새로 만들어진다. 이때 새로 만들어진 함수를 B라고 가정하면 div에 ref는 prop이 바뀌었다고 인식해 넘겨 받은 함수를 다시 실행하게 된다. 그에따라 setNode가 다시 호출되게되고 여기서 불필요한 비용이(리렌더링) 발생한다.


참고 - https://ko.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

useRef를 사용한 변수 관리

useRef는 DOM선택 목적 외에도 변수관리를 목적으로 사용하기도 한다.

주로 변수 리렌더링이 데이터 저장은 필요하지만 리렌더링이 필요하지 않은 경우 많이 사용된다.

 

useRef로 관리되는 변수


  • setTimeout, setInterval을 통해 만들어진 ID
  • scroll의 위치 저장 등

사용법

사용법은 위에 나온 DOM선택과 비슷하다.

const App = () => {
  const ref = useRef(null);

  const onClickButton = () => {
    console.log(re.current);
  };

  return (
    <div>
      <input
        type="text"
        name="keyword"
        ref={ref}
        onChange={e => {
          ref.current = e.target.value;
        }}
      />
      <button onClick={onClickButton}>입력값 확인</button>
    </div>
  );
};

추가

let으로 선언해서 사용하는거랑 다를게 없지 않나 라고 생각할 수 있는데 let으로 선언하게되면 컴포넌트가 리렌더링될 때마다 값이 초기화되지만 useRef를 사용해 관리하면 컴포넌트의 전 생애주기를 통해 값이 유지되기 때문에 리렌더링시 값이 초기화되지 않는다.

참고

값이 바뀔 때마다 렌더링이 필요한 상황에서 useState를 사용하고 그렇지 않을 경우 useRef 를 사용하면 된다고 생각하면 될 것 같다.

querySelector를 지양하고, useRef를 사용하는 이유

useRef은 항상 올바른 DOM Node로 갱신한다.

아래처럼 라이프사이클에 따라 DOM 요소를 가져오지 못하는 경우가 있다 이는 예측하지 못한 상황으로 해당 코드가 포함된 로직에 따라 큰 결함으로 이어질 수 있다.

const App = () => {
  const ref = useRef();
  const element = document.querySelector('.App');

  console.log(ref.current); // undefined
  console.log(element); // null

  useEffect(() => {
    console.log(element); // undefined
  }, [element]);

  useEffect(() => {
    console.log(ref.current); // <div class="App"></div>
  }, [ref]);

  return <div className="App" ref={ref}></div>;
};

또한 앱이 커질수록 상태는 많아지고 복잡해지게 되는데 직접적인 DOM 조작과 React 내부의 상태를 혼합하면 테스트와 디버깅이 복잡해지고 어려워진다. 그리고 리액트는 가상돔을 사용해 여러 변경 사항을 일괄 처리해 실제 돔에 한번에 적용 시킴으로써 UI 업데이트를 최적화 할 수 있게되는데 querySelector을 사용하게되면 이러한 이점을 이용할 수 없게된다.

또한 컴포넌트에서 이미 DOM 노드에 대한 참조가 있기 때문에 useRef를 사용하는게 조금 더 빠를 수 있다.
(querySelector를 사용하면 브라우저가 노드를 찾기 위해 해석하고 트리를 걸어야 하는 것들도 있기 때문)