React Hook 중의 하나로, 주로
DOM 요소
에 대한 참조를 저장하거나
렌더링 사이의 값을 유지하기 위해 사용된다.
🗣 렌더링 사이의 값을 유지하기 위해 사용한다?
컴포넌트가 다시 렌더링 될때마다
useRef로 생성된 ref 객체
가 유지된다는 의미한다.
이를 통해 이전 렌더링에서의 정보나 상태를 기억할 수 있다.
useRef
의 값은 페이지가 언마운트 되면 사라진다.
즉, useState와 생명 주기가 같다.
useRef
는ref 객체
를 반환하고
이 객체는current
라는 프로퍼티가 있다.
🗣
ref 객체
란?
useRef
또는createRef
와 같은 API를 사용해서 생성된 객체로,
주로 DOM 요소나 React 컴포넌트에 대한 참조를 저장한다.
ref 객체
는current
라는 프로퍼티를 가지고 있으며
current
프로퍼티에DOM 요소나 다른 값
을 할당할 수 있다.
기본 구조
{ current: null // 또는 다른 값 }
const number = useRef(0)
import React, { useRef, useEffect } from 'react'; const MyComponent: React.FC = () => { const inputRef = useRef<HTMLInputElement | null>(null); useEffect(() => { // DOM 요소에 직접 접근하여 포커스 설정 if (inputRef.current !== null) { inputRef.current.focus(); } }, []); return <input ref={inputRef} />; };
- 컴포넌트를
React.FC 타입
으로 명시useRef
를 호출할 때,<HTMLInputElement | null> 타입
을 지정
- 이는
input DOM 요소를 참조
하거나null 값을 가질 수 있음
을 명시합니다.useEffect
내에서inputRef.current
에 접근하기 전에null
체크
- 이는 타입스크립트가
null
또는undefined
값에 대해 안전하게 코드를 작성하도록 강제하기 때문입니다.
import React, { useRef, useEffect } from 'react'; const Timer: React.FC = () => { const timerID = useRef<NodeJS.Timeout | null>(null); useEffect(() => { timerID.current = setInterval(() => { console.log('1초 경과'); }, 1000); return () => { if (timerID.current) { clearInterval(timerID.current); } }; }, []); return ( <div> <button onClick={() => { if (timerID.current) { clearInterval(timerID.current); } }}>타이머 중지</button> </div> ); };
- 컴포넌트를
React.FC
타입으로 명시useRef<NodeJS.Timeout | null>(null)
에서는timerID.current
가NodeJS.Timeout 객체
또는null
을 가질 수 있음을 타입으로 명시
- 타이머 ID는
NodeJS의 setInterval 함수로부터 반환
되기 때문에
NodeJS.Timeout 타입
을 사용합니다.useEffect
와 버튼의onClick 이벤트 핸들러
에서
if (timerID.current)
를 사용하여 timerID.current 값이 null 또는 undefined인지 체크
- 타입스크립트에서는 이러한 체크 없이
null
이나undefined
에 접근하려 하면 오류를 발생시킬 수 있습니다.useEffect
에서 반환하는 함수를 통해 타이머를 중지합니다.
- 이 클린업 함수는
컴포넌트가 언마운트될 때 자동으로 호출
됩니다.- 이 방법을 통해
리소스 누수를 방지
할 수 있습니다.
🗣
useRef
에 타이머 ID 를 저장하지 않고useState
나일반 변수
에 저장하면 안될까?
useState
로 타이머 ID를 관리하는 예제를 살펴보자const [timerID, setTimerID] = useState(null); useEffect(() => { const id = setInterval(() => { console.log('1초 경과'); }, 1000); setTimerID(id); return () => { clearInterval(id); }; }, []);
timerId
가 먼저 null 로 초기화 되고 페이지가 렌더링된 이후
useEffect
가 실행된다.그럼 timerId 값, 즉 상태가 변경되므로 재렌더링이 일어난다.
이는 불필요한 렌더링을 초래한다.
일반 변수
에 저장하는 예제를 살펴보자useEffect(() => { const timerID = setInterval(() => { console.log('1초 경과'); }, 1000); return () => { clearInterval(timerID); }; }, []);
일반 변수를
useEffect 콜백 함수 내부에 선언
했고 타이머 ID를 할당했다.
useState
와 달리 재렌더링을 초래하지 않는다.쓰면 안된다기보다는 단점이 있다.
timerID
의 스코프를 잘 살펴보자. ->⭐️⭐️⭐️콜백함수⭐️⭐️⭐️
내부에 있다.
일반 변수의 최대 스코프 범위는함수 레벨 스코프
이다.즉, 외부에서
timerID
를 참조할 수 없다.
다른 말로 하면,Button 의 onClick 함수를 통해 타이머를 중지시킬 수 없다는 뜻이다!!!
import { useRef } from "react"; const inputRef = useRef(null);; // <Input inputRef={inputRef} /> bad <Input label="input 컴포넌트 분리" ref={inputRef} />
import { forwardRef } from "react"; const Input = forwardRef(function Input( props: { label: string }, ref: React.ForwardedRef<HTMLInputElement>, ) { const { label } = props; return ( <div> <span>{label}</span> <input ref={ref} /> </div> ); }); export default Input;
이 방식을 사용하면 Input 컴포넌트를 분리해서
전체 렌더링이 아닌 부분 렌더링을 구현할 수 있다.