간단한 사이드 프로젝트인 countdown-game을 제작하면서 dialog 와 timer를 전달을 위해 useRef를 사용하였다.
useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook입니다.(React공식문서)
JavaScript를 사용할 떄 특정 DOM을 선택해야 하는 상황에서 우리는 getElementById,
같은 DOM Selector 함수를 사용해서 DOM을 선택한다. 리액트를 사용하는 프로젝트에서도 가끔 DOM을 선택해야 하는 상황이 발생한다. DOM에 직접적으로 컨트롤 할 떄 사용하는것이 바로 useRef
이다.
useRef는 처음에 제공한 초기값으로 설정된 단일 current 프로퍼티가 있는 ref 객체를 반환한다.
ref를 변경해도 리렌더링을 촉발하지 않는다. 즉 ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합하다. Timer를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있다. ref 내부의 값을 업데이트 하려면 current 프로퍼티를 수동으로 변경해야 한다.
ref를 사용하면 다음을 보장한다.
다음 코드는 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 : 상태값들은 컴포넌트 함수를 재실행함 상태는 UI에 바로 반영되어야 하는 값들이 있을때만 사용
ref :시스템 내부에 보이지 않는 쪽, UI에 직접적인 영향을 끼치지 않는 것들에 사용
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;
TimerChallenge 컴포넌트 ⇒ ResultModal 컴포넌트 전달
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;
TimerChallenge
컴포넌트는 부모 컴포넌트에서 전달되는 title
과 targetTime
을 받아와 화면에 렌더링한다useState
훅을 사용하여 타이머가 시작되었는지(timerStarted
)와 타이머가 만료되었는지(timerExpired
) 상태를 관리한다.useRef
훅을 사용하여 타이머와 결과 모달 다이얼로그에 접근하는 데 사용되는 timer
와 dialog
를 생성한다.handleStart
함수는 타이머를 시작하고, 지정된 시간 후에 타이머가 만료되면 ResultModal
컴포넌트를 열어줍니다.handleStop
함수는 타이머를 중지한다.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;