useRef

1. useRef란?

useRef는 함수형 컴포넌트에서 참조를 생성하고 유지하는 Hook이다.

이 Hook은 주로 두 가지 용도로 사용된다.

DOM 요소에 직접 접근하거나, 컴포넌트 리렌더링을 발생시키지 않고 값을 저장하는 것이다.

 

useRef는 .current 프로퍼티를 가진 변경 가능한 객체를 반환한다.

이 객체는 컴포넌트의 전체 수명 주기 동안 유지된다.

 

ex)

import { useRef } from 'react';

function TextInputButton() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus the input</button>
    </>
  );
}

 

 

2. 기본 개념

기본 구조:

const refContainer = useRef(initialValue);

 

여기서 initialValue는 .current 프로퍼티의 초기값이 된다.

이 값은 객체, 문자열, 숫자 등 어떤 타입이든 될 수 있다.

 

주요 특징:

- 컴포넌트가 마운트될 때 생성되고 언마운트될 때까지 유지됨

- .current 프로퍼티를 변경해도 컴포넌트가 리렌더링되지 않음

- 값이 변경되어도 그것을 알려주는 알림이 없음

 

리액트는 기본적으로 선언형 프로그래밍 방식으로 상태만 정의하면 알아서 DOM이 업데이트 된다.

하지만 useRef는 명령형 프로그래밍 방식으로 DOM요소에 직접 접근해서 DOM메서드를 직접 호출하거나 속성을 직접 조작할 수 있다.

포커스처럼 선언적 모델로 다루기 어려운 작업이나 외부 라이브러리와 통합할 때 유용하다.

 

 

3. useRef와 DOM

3.1. 실제 DOM vs 가상 DOM

React에서 useRef의 역할을 이해하기 위해 DOM 처리 방식을 이해하는 것이 중요하다.

 

가상 DOM(Virtual DOM):

 - React가 메모리에 유지하는 실제 DOM의 가벼운 복사본이다.

 - React는 상태 변경 시 먼저 가상 DOM을 업데이트한다.

  

실제 DOM(Real DOM):

 - 브라우저에 실제로 렌더링되는 DOM 요소이다.

 - React는 가상 DOM과 실제 DOM을 비교한 후 필요한 부분만 실제 DOM에 업데이트한다.

 

이때 useRef로 참조하는 DOM 요소는 실제 DOM 요소이다.

그렇기 때문에 React의 선언적 패러다임을 벗어나 명령형으로 DOM을 직접 조작하게 된다.

 

3.2. DOM 요소 직접 조작

useRef를 사용하면 DOM 요소에 직접 접근하여 다양한 작업을 수행할 수 있다.

 

ex)

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // 컴포넌트가 마운트된 후 input에 자동으로 포커스
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

 

 

4. useRef와 리렌더링

4.1. useState와의 차이점

useState와 useRef는 모두 값을 저장하는 데 사용되지만, 중요한 차이가 있다.

useState는 값을 변경하면 컴포넌트가 리렌더링되지만, useRef는 값이 변경되지 않는다.

 

ex)

const [count, setCount] = useState(0);
const increment = () => setCount(count + 1); // 컴포넌트가 리렌더링됨

const countRef = useRef(0);
const increment = () => { countRef.current += 1; }; // 컴포넌트가 리렌더링되지 않음

 

4.2. 리렌더링이 발생하지 않는 이유

useRef가 리렌더링을 발생시키지 않는 이유는 두 가지 주요 요소 때문이다.

 

1) React의 설계 의도: React는 의도적으로 useRef의 .current 속성 변화를 감지하거나 이에 반응하지 않도록 설계되었다. 이것이 useState와 같은 다른 상태 관리 훅과의 중요한 차이점이다.

 

2) 참조의 안정성: useRef가 반환하는 객체 자체(참조)는 컴포넌트의 수명 동안 항상 동일하다. 그 안의 .current 속성 값을 변경해도 객체 자체는 변경되지 않는다.

 

const ref = useRef(0);

ref.current = 5; // 객체 내부의 current 속성만 변경하며, ref 객체 자체는 그대로

 

이러한 특성 덕분에 리렌더링 없이 값을 저장하거나 변경해야 할 때 유용하다.

 

 

5. 커스텀 컴포넌트에 전달

커스텀 컴포넌트에 forwardRef를 사용해 ref를 전달할 수 있다.

참고로 리액트 19부터는 forwardRef를 사용하지 않고 ref를 바로 전달할 수 있다.

 

ex)

const CustomInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

function Form() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <>
      <CustomInput ref={inputRef} placeholder="입력하세요..." />
      <button onClick={focusInput}>포커스</button>
    </>
  );
}

 

 

6. 주의사항

1) 초기값을 넣어주지 않으면 null 체크 필요

컴포넌트가 처음 렌더링될 때 컴포넌트를 실행한 후 DOM을 렌더링한다.

하지만 ref는 DOM을 직접 참조하므로 컴포넌트 실행 단계에서는 값이 존재하지 않아 에러가 발생한다.

그러므로 초기값을 넣어주거나 null 체크를 해줘야 한다.

 

   const doSomething = () => {
     if (inputRef.current) {
       inputRef.current.focus();
     }
   };

 

2) 불필요한 DOM 조작 피하기

DOM을 직접 조작하면 React의 선언적 흐름을 깨뜨리고, 가상 DOM과 실제 DOM 사이에 불일치가 생길 수 있다.

그러므로 React의 선언적 접근 방식을 최대한 활용하고, 필요한 경우에만 useRef로 DOM을 직접 조작하는 것이 좋다.

 

3) ref 초기화 시점 고려

ref는 렌더링 중에는 설정되지 않는다.

앞서 말했듯 DOM 요소는 렌더링 후에 ref에 할당된다.