useRef 와 forwardRef

5_wintaek·2024년 3월 27일
0
post-custom-banner

ref 란 무엇인가 ?

간단한 사이드 프로젝트인 countdown-game을 제작하면서 dialog 와 timer를 전달을 위해 useRef를 사용하였다.

useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook입니다.(React공식문서)

JavaScript를 사용할 떄 특정 DOM을 선택해야 하는 상황에서 우리는 getElementById, 같은 DOM Selector 함수를 사용해서 DOM을 선택한다. 리액트를 사용하는 프로젝트에서도 가끔 DOM을 선택해야 하는 상황이 발생한다. DOM에 직접적으로 컨트롤 할 떄 사용하는것이 바로 useRef이다.

  • Ref는 상태값들처럼 유실되지 않는다.
  • 컴포넌트 함수를 다시 실행하도록 하지 않는다.
  • Ref는 UI와 직접적 영향이 없는 경우 사용하는것이 좋은데, timer 자체가 UI와 직접적 영향이 없으므로 사용하기 좋은 사례

사용법. ref 로 값 참조하기

useRef는 처음에 제공한 초기값으로 설정된 단일 current 프로퍼티가 있는 ref 객체를 반환한다.
ref를 변경해도 리렌더링을 촉발하지 않는다. 즉 ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합하다. Timer를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있다. ref 내부의 값을 업데이트 하려면 current 프로퍼티를 수동으로 변경해야 한다.

ref를 사용하면 다음을 보장한다.

  • (렌더링할 때마다 재설정되는 일반 변수와 달리)리렌더링 사이에 정보를 저장할 수 있다.
  • 리렌더링을 촉발하는 state 변수와 달리 변경해도 리렌더링을 촉발하지 않는다.
  • 각각의 컴포넌트에 로컬로 저장된다.

다음 코드는 React 공식문서에 있는 코드이다.
ref값을 참조하여 버튼을 클릭할 떄 마다 횟수가 증가한다.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

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

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

state 와 Ref 의 차이점

state : 상태값들은 컴포넌트 함수를 재실행함 상태는 UI에 바로 반영되어야 하는 값들이 있을때만 사용
ref :시스템 내부에 보이지 않는 쪽, UI에 직접적인 영향을 끼치지 않는 것들에 사용

forwardRef 란?

forwardRef 를 사용하면 컴포넌트가 ref.를 사용하여 부모 컴포넌트의 DOM 노드를 노출할 수 있습니다.

ref(참조)는 다른 컴포넌트로 전달할 수 없다. 대신 만약에 참조를 컴포넌트에서 다른 컴포넌트로 전달하고 그 값을 사용하고 싶다면 forwardRef 를 사용

open을 사용해서 modal창을 이용하면 modal창 background 색상이 회색이 되고 싶게 만들고 싶은데 , ref와 forwardRef 를 이용해서 TimerChallenge.jsx에 있는 ref값을 가져와 만들것이다.

수정전 코드

function ResultModal({result, targetTime}) {
  return (
    <dialog open>
      <h2>You {result}</h2>
      <p>The target time was {targetTime} seconds.</p>
      <p>You Stop the timer with X seconds lefts</p>
      <form method="text">
        <button>Close</button>
      </form>
    </dialog>
  );
}

export default ResultModal;

예제로 보는 forwwardRef 사용법

TimerChallenge 컴포넌트 ⇒ ResultModal 컴포넌트 전달

  • 컴포넌트 함수를 감싸야 한다.
  • 첫번째는 props 받지만, 두번째는 ref 매개변수를 받는다.
  • forwardRef를 사용하여 ref를 받아오고, dialog 엘리먼트에 ref={ref}를 설정하여 부모 컴포넌트에서 ref를 통해 접근할 수 있도록 하였다. 이로 인해 TimerChallenge 컴포넌트에서 dialog.current.showModal();을 호출할 때 showModal 함수가 호출되어야 한다.

수정후 ResumtModal

import {forwardRef} from 'react';

const ResultModal = forwardRef(function ResultModal({result, targetTime}, ref) {
  return (
    // dialog 는 기본값이 close라 open 이라는 것을 넣어줘야함
    <dialog ref={ref} className="result-modal">
      <h2>You {result}</h2>
      <p>The target time was {targetTime} seconds.</p>
      <p>You Stop the timer with X seconds lefts</p>
      <form method="dialog">
        <button>Close</button>
      </form>
    </dialog>
  );
});

export default ResultModal;
  • showModal 은 표준 브라우저 기능 중 하나이다.
  • TimerChallenge 컴포넌트는 부모 컴포넌트에서 전달되는 titletargetTime을 받아와 화면에 렌더링한다
  • useState 훅을 사용하여 타이머가 시작되었는지(timerStarted)와 타이머가 만료되었는지(timerExpired) 상태를 관리한다.
  • useRef 훅을 사용하여 타이머와 결과 모달 다이얼로그에 접근하는 데 사용되는 timerdialog를 생성한다.
  • handleStart 함수는 타이머를 시작하고, 지정된 시간 후에 타이머가 만료되면 ResultModal 컴포넌트를 열어줍니다.
  • handleStop 함수는 타이머를 중지한다.
  • JSX를 통해 화면에는 도전 과제의 제목, 목표 시간, 시작/중지 버튼 등이 렌더링되며, 만료되었을 때 ResultModal 컴포넌트가 나타난다.
import {useState, useRef} from 'react';
import ResultModal from './ResultModal';

function TimerChallenge({title, targetTime}) {
  const timer = useRef();
  const dialog = useRef();
  const [timerStarted, setTimerStarted] = useState(false);
  const [timerExpired, setTimerExpired] = useState(false);

  const handleStart = () => {
    timer.current = setTimeout(() => {
      setTimerExpired(true);
      dialog.current.showModal();
    }, targetTime * 1000);

    setTimerStarted(true);
  };

  const handleStop = () => {
    clearTimeout(timer.current);
  };

  return (
    <>
      <ResultModal ref={dialog} targetTime={targetTime} result="lost" />
      <section className="challenge">
        <h2>{title}</h2>
        <p className="challenge-time">
          {targetTime} second{targetTime > 1 ? 's' : ''}
        </p>
        <p>
          <button onClick={timerStarted ? handleStop : handleStart}>
            {timerStarted ? 'Stop' : 'Start'}
          </button>
        </p>
        <p className={timerStarted ? 'active' : undefined}>
          {timerStarted ? 'Time is running out ~' : 'Timer inactive'}
        </p>
      </section>
    </>
  );
}

export default TimerChallenge;
profile
물음표를 느낌표로 바꾸는 개발자
post-custom-banner

0개의 댓글