본문 바로가기
카테고리 없음

[React] useRef- 언제, 왜, 어떻게 써야 하는 걸까?

by forevero3o 2025. 2. 20.

 

 

 

 

📌 useRef : 컴포넌트가 일부 정보를 "기억"해야하지만, 렌더링을 유발하지 않도록 하려면 사용하세요! React hook

1. useRef 기본 사용

import { useRef } from "react"

const App = () => {

  const ref = useRef(0);

  console.log(ref)

  return (
    <div>App</div>
  )
}

export default App

✅ 1. import useRef하기

✅ 2. useRef는 유일한 인자로 초기값을 전달합니다. 

✅ 3. console.log에 찍힌 useRef를 저장한 값은 객체를 반환합니다.

 

현재 ref는 number type을 가리키지만, state처럼 문자열, 객체, 함수 등 모든 것을 가리킬 수 있습니다. 

state는 useState 훅의 set 함수를 이용해 값을 변경하지만 useRef는 ref.current ref 객체의 current키를 통해 읽고 수정할 수 있는 값입니다. 

 

⭐state와 가장 큰 차이는 state는 변경하면 컴포넌트가 다시 렌더링 되지만, ref 값을 변경하면 다시 렌더링이 발생하지 않습니다.

 


2. useRef 사용해보기

import { useRef } from "react";

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

  function handleClick() {
    ref.current = ref.current + 1;
    alert("You clicked " + ref.current + " times!");
  }

  return (
    <>
      <h1>{ref.current}</h1>
      <button onClick={handleClick}>Click me!</button>
    </>
  );
};

export default App;

 

✅ 버턴을 클릭하면 ref.current 값은 올라가지만 화면에는 보이지 않는다 = 렌더링이 되지 않는다

 

📢 ref를 화면에 렌더링 시키려면 리렌더링 조건 ( State가 변화, 부모 컴포넌트로 부터 발생한 리렌더링 등)

을 만족하는 다른 조건이 필요합니다.

 

📌 useState와 useRef를 활용한  Timer clear (타이머 종료) 예제

더보기

 

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const [timerId, setTimerId] = useState(null);

  function handleStart() {
    // 기존 타이머가 있다면 먼저 정지
    if (timerId) {
      clearInterval(timerId);
      setTimerId(null);
    }

    // 타이머 시작
    const start = Date.now();
    setStartTime(start);
    setNow(start);

    const id = setInterval(() => {
      setNow(Date.now());
    }, 1000);

    // setInterval의 ID를 state에 저장
    setTimerId(id);
  }

  function handleStop() {
    // 타이머 정지
    clearInterval(timerId);
    setTimerId(null);
  }

  let secondsPassed = 0;
  if (startTime !== null && now !== null) {
    secondsPassed = (now - startTime) / 1000;
  }
  console.log("🎨리렌더링 발생")

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </>
  );
}
import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  // setInterval의 ID를 저장할 참조(Ref)
  const intervalIdRef = useRef(null);

  function handleStart() {
    // 기존에 동작 중이던 타이머가 있으면 먼저 정지
    if (intervalIdRef.current) {
      clearInterval(intervalIdRef.current);
    }

    setStartTime(Date.now());
    setNow(Date.now());

    // 새로운 타이머 시작
    intervalIdRef.current = setInterval(() => {
      setNow(Date.now());
    }, 1000);
  }

  function handleStop() {
    // 타이머 정지
    clearInterval(intervalIdRef.current);
    intervalIdRef.current = null;
  }

  let secondsPassed = 0;
  if (startTime !== null && now !== null) {
    secondsPassed = (now - startTime) / 1000;
  }
  console.log("🎨리렌더링 발생")

  return (
    <>
      <h1>Time passed: {secondsPassed}</h1>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </>
  );
}

 

Timer를 종료시키는 방법은 Timer 함수들이 반환하는 ID를 저장한뒤 clear 함수들을 이용하여 타이머를 제거하는 방법이다. 

 

🎈 공식문서를 통해 setInterval, clearInterval이 어떻게 동작하는지 확인하세요!

https://developer.mozilla.org/ko/docs/Web/API/Window/setInterval

https://developer.mozilla.org/ko/docs/Web/API/Window/clearTimeout

 

🙄두 개의 작성된 Timer가 동작하는 것의 차이를 예측해보세요

 

State 방식의 Timer 정지

1. 타이머는 Start 버튼을 누르면 현재시간과 시작시간은 SetNow, setStartTime에 기록합니다.

2. setInterval 함수를 통해 0.5초에 한번 현재시간을 업데이트하는 비동기 함수를 등록합니다.

3. setTimerId를 통해 id값을 State로 관리합니다. 

4. Stop 버튼을 누르면 State로 저장한 timerId를 clear 함수를 통해 정지시킵니다.

 

 

useRef 방식의 Timer 정지

1. 타이머는 Start 버튼을 누르면 현재시간과 시작시간은 SetNow, setStartTime에 기록합니다.

2. setInterval 함수를 통해 0.5초에 한번 현재시간을 업데이트하는 비동기 함수를 등록합니다.

3. ref.current 값을 통해 id값을 저장합니다.

4. Stop 버튼을 누르면 ref.current로 저장한 timerId를 clear 함수를 통해 정지시킵니다.

 

 

(좌) State로 TimerId를 저장하여 초기화 시키는 경우에는 Stop 버튼을 누르면 SetTimerId를 호출하기 때문에

State 변경이 발생한다. = 리렌더링이 발생 ⭕

 

(우) useRef로 TimerId를 저장하여 초기화 시키는 경우에는 Stop 버튼을 누르면 ref.current 값을 클리어 한다.

State를 변경이 발생하지 않는다. = 리렌더링이 발생하지 ❌

 

 

📌 React가 관리하는 DOM 노드에 접근하기

일반적으로 React는 DOM 요소 변경을 자동으로 처리하기 때문에 컴포넌트의 요소에 직접 조작해야하는 필요는 없습니다. 하지만 스크롤의 위치나 React가 관리하는 DOM요소에 접근해야 하는 경우가 있습니다.

예)로그인 페이지에 로드하자마자 입력창에 Focus 시키기 

 

새로고침해서 화면이 로드 되자마자 Input이 활성화 된다.

import {useRef, useEffect } from 'react';

export default function FocusOnLoad() {
  const inputRef = useRef(null);

  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []); // 의존성 배열을 빈 배열로 두면, 컴포넌트가 마운트될 때 한 번만 실행됩니다.
  console.log("🎨3231")

  return (
    <div>
      <input
        type="text"
        ref={inputRef}   // 이 input 요소가 마운트되면, useEffect에서 focus가 실행됩니다.
        placeholder="페이지 로드 시 포커스"
      />
    </div>
  );
}

 

 

📌버튼을 누르면 배경색을 바꾸는 예제

import React, { useRef } from 'react';

function HighlightOnClick() {
  const boxRef = useRef(null);

  const handleHighlight = () => {
    if (boxRef.current) {
      // DOM API로 스타일을 직접 변경
      boxRef.current.style.backgroundColor = 'yellow';
      setTimeout(() => {
        boxRef.current.style.backgroundColor = 'white';
      }, 1000);
    }
  };

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: '200px',
          height: '100px',
          backgroundColor: 'white',
          border: '1px solid #000',
          marginBottom: '1rem',
        }}
      >
        클릭 시 하이라이트
      </div>
      <button onClick={handleHighlight}>하이라이트</button>
    </div>
  );
}

export default HighlightOnClick;

📣  마무리 

useRef를 사용하면 렌더링과 상관없는 부수효과들을 처리할 수 있습니다. 렌더링이 필요없는 상황에 활용하여 프로젝트를 최적화하는데 사용해보세요!