본문 바로가기

JavaScript/React

useRef란?

안녕하세요 :)

 

오늘은 React Hooks 중 하나인 useRef에 대해 알아보겠습니다. React 개발에서 상태 관리는 중요하게 적용되는 요소 중 하나인데, 상태 변화가 화면에 바로 반영되지 않아도 되는 상황들이 있습니다. 그럴 때는 불필요하게 렌더링이 되는 걸 막는 방법이 필요하게 됩니다.

출처: https://purecode.ai/blogs/reacts-useref

 

이 글에서는 useRef라는 훅을 소개하고, 언제 어떻게 사용하는지, 같은 상태 변화를 담당하고 있는 useState와는 뭐가 다른지를 살펴보겠습니다.

 

useRef란?

React 공식 문서에서는 useRef를 '렌더링에 필요하지 않은 값을 참조할 수 있게 해주는 React Hook'이라고 소개하고 있습니다.

useRef is a React Hook that lets you reference a value that’s not needed for rendering.

 

'렌더링에 필요하지 않은 값을 참조', 이게 무슨 말일까요?

조금 쉽게 풀어서 말하자면, '화면에 변화가 없어도, 값을 계속해서 기억해두고 나중에 쓸 수 있다' 가 될 수 있습니다. 즉, 값이 바뀌더라도 그걸 바로 화면에 보여주거나 업데이트하지 않고 조용히 저장해 놓고, 필요할 때 꺼내서 사용할 수 있는 도구라는 뜻입니다.

 

useRef는 언제 사용될까요?

React에서는 기본적으로 상태나 props의 변화는 컴포넌트를 재렌더링하여, 그 결과 DOM을 다시 그리게 됩니다. 그러나 useRef는 값이 변하더라도 컴포넌트를 재렌더링하지 않고, current로 참조된 DOM 요소를 직접 조작할 수 있는 방법을 제공합니다.

DOM 요소: HTML 태그 트리, 출처: https://myeongsu0257.tistory.com/41

  1. DOM 요소에 접근이 필요할 때 
    • 특정 입력 필드에 포커스를 주거나, 비디오 플레이어의 재생 위치를 조작해야 할 때 useRef를 사용하면 DOM에 직접 접근할 수 있습니다. 이때는 값이 바뀌어도 화면에 즉각적인 변화가 필요 없고, DOM 요소를 조작하는 것만이 목적일 때입니다.
  2. 렌더링 없이 상태를 추적하고 싶을 때
    • 어떤 값이 계속 바뀌지만, 그 값이 바뀔 때마다 화면에 다시 그릴 필요가 없는 경우에도 useRef가 적합합니다. 예를 들어, 사용자가 텍스트를 입력하는 동안 현재 입력된 글자 수를 내부적으로 추적하면서 그 값을 굳이 화면에 보여줄 필요가 없을 때 사용하여 글자 수를 제한할 수도 있죠.

이처럼 위와 같은 동작이 필요할 때 useRef가 사용됩니다.

 

useRef 기본 사용법

useRef는 current속성에 참조하려는 값이나 DOM 요소를 저장합니다. 그리고 이 current는 컴포넌트가 재렌더링되어도 변하지 않고 유지됩니다.

import { useRef } from 'react';

export default function RefExample() {
  const inputRef = useRef(null); // { current: null } 초기화
  
  const focusInput = () => {
    inputRef.current.focus(); // input 요소에 포커스 설정
  }

  return (
    <div>
      <input ref={inputRef} type="text" /> {/* input 요소에 대한 참조 */}
      <button onClick={focusInput}>Focus the input</button>
    </div>
  );
}

 

current의 역할이 무엇일까요? 위 예시 코드를 보며 알아보겠습니다.

위 예시는 '버튼을 클릭하면 input태그에 포커스가 이동하는 동작을 하게 되는 코드'입니다.

  1. 초기값 지정
    • useRef는  { current: initiaValue } 형식으로 변환되며, 초기값은 null로 설정됩니다. DOM요소가 렌더링된 후에 inputRef.current가 해당 DOM 요소소(HTML 태그)를 가리키게 됩니다.
  2. 최근값 유지
    • useRef로 생성된 객체는 컴포넌트가 다시 렌더링되더라도 초기화되지 않고 계속 같은 객체를 가리킵니다. 이는 처음 마운트 시 가리키게 됐던 요소를 계속 가리키고 있다는 뜻입니다.
    • 즉, current에 저장된 값은 렌더링과 상관없이 유지되며, 값이 바뀌어도 컴포넌트가 다시 렌더링되지 않습니다.

 

왜 useRef에 current가 필요할까요?

출처: https://dmitripavlutin.com/react-useref/

useRef의 current는 렌더링과 무관하게 값이나 DOM요소를 추적하고 관리하는 걸 용이하게 해줍니다. 일반적인 상태값 변경 훅인 useState를 사용하면 값이 변경될 때마다 컴포넌트가 다시 렌더링되지만, useRef의 current 속성은 변경되더라도 컴포넌트를 다시 렌더링 하지 않고, 값만 내부적으로 저장됩니다.

 

useState와 useRef의 차이점

useState와 useRef는 상태 관리라는 점에서 비슷하지만, 사용 목적과 렌더링에 미치는 영향이 다릅니다. 둘이 어떻게 다른지 보도록 하겠습니다.

  • useState
    • 상태 값이 변경되면 컴포넌트가 다시 렌더링됩니다. 즉, UI에 변경 값이 바로 반영되어야 할 때 사용됩니다.
  • useRef
    • 상태가 변하더라도 컴포넌트가 다시 렌더링되지 않고, 그 값은 내부적으로만 유지됩니다. 렌더링 없이 조용히 값을 유지하거나 DOM에 접근할 때 사용됩니다.

이렇게 차이를 보아도 제대로 감이 안 잡히시죠? useRef 실제 사용 예시를 보시죠.

 

useRef 사용 예시

대표적인 예시인 애니메이션, 드래그 앤 드롭, 그리고 비디오 타임코드를 제시해보겠습니다.

 

1. 애니메이션 관리

import { useRef } from 'react';

export default function AnimationExample() {
    const animatationRef = useRef(0);
    
    const startAnimation = () => {
        animatationRef.current = requestAniamtation(() => {
            console.log("애니메이션 프레임 확인", animationRef.current);
            startAnimation(); // 계속해서 애니메이션 프레임 추적
        };   
    };
    
    return(
        <button onClick = { startAnimation }> 애니메이션 관리 예시 </button>
    )

}

실제 서비스에서 애니메이션 관리를 왜 사용하나요?

  • 애니메이션은 웹사이트나 앱에서 부드러운 UX를 제공하는데 중요하게 작용하는 요소입니다. 특히 게임, 로딩 화면 또는 인터랙티브 웹 애플리케이션에서 애니메이션은 계속해서 상태가 변하지만, 이 변화를 매번 렌더링할 필요는 없습니다.
  • 여기서는 useRef로 애니메이션 프레임을 추적하여 계속해서 업데이트합니다. 만약 useState를 사용했다면 프레임이 변경될 때마다 렌더링이 발생해 성능이슈가 발생할 수 있습니다.
  • 결론적으로 여기서 useRef는 애니메이션의 프레임을 추적 관리하면서 불필요한 렌더링을 방지하는데 사용됩니다.
  • 실사례로 로딩 애니메이션, 스크롤 애니메이션 등 사용자에게 부드러운 인터렉션 효과를 제공할 때 유용하게 사용됩니다.

 

2. 드래그 앤 드롭 위치 추적

import { useRef } from 'react';

export default function DragExample(){
    const dragPositionRef = useRef({ x: 0, y: 0});
    
    const handleDrag = (e) => {
        dragPositionRef.current = { x: e.clientX, y: e.clientY };
        console.log("현재 드래그 위치: ", dragPositionRef.current);
    };
    
    return(
        <div onMouseMove = { handleDrag } >
            드래그 하세용
        </div>
    )

}

드래그 앤 드롭 위치 추적에는 useRef가 왜 쓰일까요?

  • 드래그 앤 드롭은 우리가 흔히 파일 업로드하거나, 화면에서 어떤 위젯(아이콘, 박스 등)을 옮길때, 또는 캔버스 기반의 디자인 툴에서 많이 사용되는 기능입니다. 
  • 이 과정에서 사용자가 마우스를 클릭한 상태로 움직일 때마다 현재 마우스가 어디에 있는지 계속 추적해야 합니다. 하지만 그 위치 정보를 매 순간 화면에 보여주거나 업데이트할 필요는 없죠. 중요한 건 최종적으로 사용자가 드래그를 끝냈을 때(마우스를 놓았을 때) 해당 위젯이 어디에 있는지이기 때문입니다.
  • useRef는 드래그 중인 위치를 렌더링 없이 추적할 수 있게 해 줍니다. 즉, UI에 영향을 주지 않으면서 드래그 위치를 추적할 수 있죠.
  • 드래그 중인 위치를 추적하고 나서 나중에 드래그가 끝났을 때만 최종 위치를 UI에 반영할 수 있어 서비스 성능에 도움을 줍니다.

 

3. 비디오 재생 시간 유지

import { useRef } from 'react';

export default function VideoExample() {
    const videoRef = useRef(null);
    const currentTimeRef = useRef(0);
    
    handleVideoQualityChange = (newQuality) => {
        // 비디오의 현재 재생 시간을 저장 
        // currentTime: video 태그의 속성 중 하나, 오디오 재생의 현재 시간을 나타냄
        currentTimeRef.current = videoRef.current.currentTime;
        
        // 해상도 변경 콘솔 출력
        console.log(`해상도가 ${newQuality}로 변경되었습니다.`)
        
        // 새 해상도에서 재생을 계속하려면 비디오 재생 시간을 다시 설정
        videoRef.current.currentTime = currentTimeRef.current;
        
        //비디오 재생
        // play(): <video> or <audio> 태그에서 제공하는 메서드
        videoRef.current.play();
    };
    
    return(
        <div>
            <video ref = { videoRef } width = "600" controls>
                <source src = "https://example/html/mov_ex.mp4" type = "video/mp4">
                    브라우저가 비디오 태그를 지원하지 않습니다.
                </source>
            </video>
            
            <div>
                {/* 해상도 변경 버튼들 */}
                <button onClick={() => handleQualityChange('720p')}>720p로 변경</button>
                <button onClick={() => handleQualityChange('1080p')}>1080p로 변경</button>
                <button onClick={() => handleQualityChange('4K')}>4K로 변경</button>
            </div>
        </div>
    )
}

useRef를 사용하면 동영상 재생 시간을 유지할 수 있을까요?

  • 유튜브와 같은 비디오 플레이어에서 해상도를 변경하너나 다른 조작을 할 때, '현재 재생 중인 위치(타임 코드)'를 유지하는 것이 중요합니다.
  • 사용자가 해상도를 바꾸더라도, 이전 위치에서 그대로 이어서 재생하기 때문이죠.
  • useRef를 사용하면 비디오의 현재 재생 시간을 저장하고, 해상도 변경 후 렌더링 없이 그 위치에서 이어서 재생할 수 있습니다. 중간에 렌더링이 되더라도 값이 유지되기 때문에, 해상도를 바꿔도 재생 시간이 초기화되지 않고 이어집니다.
  • useRef로 기억한 타임코드(재생 위치)를 바탕으로, 새로운 해상도의 비디오 파일이 해당 타임코드에서 재생을 이어갑니다. 여기서 중요한건, 비디오 플레이어는 그 타임코드에 해당하는 데이터를 로드해야 한다는 것입니다.
  • 비디오 플레이어는 비디오 파일이 변경되면 타임코드 위치에서부터 버퍼링을 시작하며, 필요한 구간만 버퍼링하여 효율적으로 처리합니다. 즉, 비디오 전체를 로드할 필요 없이 해당 구간의 데이터를 먼저 로드해 재생을 이어가도록 할 수 있습니다.

 

끝내는 말

오늘은 useRef에 대해서 알아보았습니다. useRef는 렌더링 없이도 상태를 관리할 수 있고, DOM 요소를 직접 조작할 수 있는 유용한 Hook입니다. useState와 많이 헷갈릴 수 있다고 생각하는데 UI에 바로 적용이 되느냐, 안 되느냐를 따져보면 쉽게 이해하실 수 있을 것 같습니다. 상태 변화가 빈번하지만 UI 렌더링을 최소화해야 하는 환경이나 비동기 작업이 많고, 아니면 상태를 조용히 추적하면서 기능을 제공하고 싶을 때 사용하시면 좋을 거 같습니다!

 

읽어주셔서 감사합니다:)

 

'JavaScript > React' 카테고리의 다른 글

React 컴포넌트란 무엇인가.  (2) 2024.12.29
React에서 Debounce 사용하기  (1) 2024.11.08
Context API로 전역 상태 관리하기  (2) 2024.10.28
useEffect: 클린업 함수의 필요성  (4) 2024.10.14
useEffect란?  (1) 2024.10.08