[React] useRef를 언제 사용하면 좋을까?

Joo·2024년 3월 21일

React

목록 보기
10/11
post-thumbnail

++ 리액트를 더 잘 쓰기 위한 정리 시리즈

유저 이름을 설정하면 그 이름을 보여주는 컴포넌트를 만든다고 생각해보자.
아래 코드처럼 onChange 함수와 useState를 사용하면, 사용자가 이름을 입력할때마다 렌더링이 발생한다.

import { useState } from "react";
export default function Player() {
  const [name, setName] = useState();

  const handleChangeName = (event) => {
    setName(event.target.value);
  };

  return (
    <section id="player">
      <h2>Welcome {name ?? "Unknown"}</h2>
      <p>
        <input type="text" onChange={handleChangeName} value={name} />
        <button>Set Name</button>
      </p>
    </section>
  );
}

useRef를 사용하는 첫번째 경우

우리가 필요한건 최종적으로 저장할 이름만 가져오면 되는 일이기에 위 코드 방식은 불필요하게 렌더링을 발생시킨다.

이럴때, useRef를 사용하면 이런 문제를 해결할 수 있다. input DOM 요소를 직접 참조해서 값을 읽어올 것이다.

useState와 같이 useRef 훅을 import 하고,
const nameRef = useRef() 로 정의한 변수를 input 요소의 ref 속성에서 사용하고 있다.

그리고 실제 input에 입력한 값을 가져오기 위해 nameRef.current.value 를 사용했다.

import { useState, useRef } from "react";
export default function Player() {
  const nameRef = useRef();
  const [name, setName] = useState();

  const handleSetName = () => {
    setName(nameRef.current.value);
  };

  return (
    <section id="player">
      <h2>Welcome {name ?? "Unknown"}</h2>
      <p>
        <input ref={nameRef} type="text" />
        <button onClick={handleSetName}>Set Name</button>
      </p>
    </section>
  );
}

useRef를 사용하는 두번째 경우

타이머를 사용하는 경우를 예로 들어보자.
setTimeout() 이라는 js 내장 객체를 사용해 타이머를 구현할 때, 타이머를 생성하고 타임오버되면 타이머를 종료(파괴?)해야 한다.

그런데 타이머를 생성하고 해당 타이머를 종료해야 하는데, 다른 모종의 이유로 렌더링이 필요한 상황이 생긴다면 타이머는 원래 가리키던 값일까?
만약 const나 let으로 타이머 변수를 만들었다면 리렌더링 되면서 원래 가리키던 타이머가 아닌 재정의한 새로운 타이머가 된다.

이럴때도 useRef를 사용할 수 있다!

import { useState, useRef } from "react";
export default function Timer() {
  const timer = useRef();
  const [time, setTime] = useState(5);
  
  // let timer; // 리렌더링 되면 타이머는 초기화됨

  const handleStartTimer = () => {
    timer.current = setTimeout(() => {
      setTime(0); // 5초 뒤에 time을 0으로 바꾸면 리렌더링
    }, 5 * 1000);
  };

  const handleStopTimer = () => {
    clearTimeout(timer.current);
  };
  
  // 리렌더링 되더라도 start 버튼을 눌렀을 때, 정의된 타이머를 가리킴
  if (time === 0) clearTimeout(timer.current);

  return (
    <section>
      <p>Timer</p>
      <button onClick={handleStartTimer}>Start Timer</button>
      <button onClick={handleStopTimer}>Stop Timer</button>
    </section>
  );
}

정리하며,,

주로 useState를 사용하면서 useRef가 어떤 역할을 하는지 정확하게 몰랐었다. 이번 기회에 useRef를 정리하면서 어떤 훅이든 꼭 필요한 부분에 사용해야 한다는 것을 알게 되었다. 난무하듯 사용했을 때, 오히려 코드가 복잡해지거나 예상치 못한 버그가 발생할 수도 있다

profile
한 줄이 모여 책이 되듯 기록하기

0개의 댓글