
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 컴포넌트를 분리해서
전체 렌더링이 아닌 부분 렌더링을 구현할 수 있다.