본문 바로가기

개발자 강화/프론트엔드

[매일메일+개발] useRef에 대하여 + 버튼 '따닥' 방지 실예제

useRef()

리렌더링 없이 값을 저장하거나, DOM 요소에 직접 접근할 수 있게 해주는 React Hook

변경은 되지만 화면에 영향을 주지 않는 값을 관리할 때 쓰면 됨

 

[1] DOM 요소 접근에 사용하는 경우

  • React에서는 대부분 DOM 조작이 필요없지만
  • 포커스를 주거나, 특정 위치로 스크롤하거나, canvas, video 같은 경우 DOM 요소에 직접 접근해야 함
const inputRef = useRef(null);

useEffect(()=> {
inputRef.current.focus(); // 컴포넌트 마운트 시 input에 포커스를 맞춘다
},[]);

return <input ref={inputRef} />;

 

  • 입력(input) 필드에 focus를 설정하고 싶을 때, useRef()를 사용해 input 요소에 접근할 수 있음
  • ref={inputRef}로 접근하면, inputRef.current가 실제 DOM 요소를 가리킴
  • useEffect 안에서 접근해야 함 (랜더링 중에는 DOM이 만들어지기 전이므로)

[2] 랜더링 없이 값 저장하기

  • React의 useState는 값이 바뀔 때마다 화면이 리렌더링 됨
  • 어떤 값들은 렌더링할 필요가 없는 정보일 때도 있음
  • 타이머 ID, 이전 값, 외부 라이브러리 인스턴스 같은 경우 useRef로 저장해 변경되도 리렌더되지 않도록 막음
const timeRef = useRef(null)l;

const startTimer = () => {
	timerRef.current = setInterval(() => {
    console.log('타이머 실행')
    }, 1000);
};

const stopTimer = () => {
	clearInterval(timerRef.current); // 타이머 정지
};

 

  • timerRef.current에 타이머 ID를 저장하지만, 값이 바뀌어도 컴포넌트는 리렌더링 되지 않음
  • useState였다면 매번 리렌더링 되어 불필요한 연산이 발생했을 것

 

[3] 자주 쓰는 패턴

이전 값 기억하기

리렌더마다 변하는 값의 직전 상태를 저장하고 싶을 때 useRef 사용

const prevValue = useRef(value);

useEffect(() => {
prevValue.current = value;
}, [value]);

// prevValue.current는 이전 렌더의 value

 

스크롤 조작

const boxRef = useRef(null);

const scrollToBottom = () => {
boxRef.current?.scrollTo({top: 99999, behavior: "smooth"});
};

 

외부 라이브러리 인스턴스 저장

const playRef = useRef(null);

useEffect(()=> {
playRef.current = new SomeVideoPlayer();
return () => playRef.current.destroy();
}, []);

 

[4] useState와의 차이

상황 useState useRef
값이 바뀌면 리렌더링 됨 o x
값이 렌더링에 영향을 줌 o x
DOM에 직접 접근 가능 x o
외부 라이브러리, 타이머 관리 (사용할 순 있는데 불필요한 연산 발생 가능) o

 

[5] 주의할 점

  • useRef는 렌더 중에는 사용하지 말고, useEffect나 이벤트 핸들러 안에서만 다뤄야 함
  • ref.current 값이 바뀌어도 화면은 다시 그려지지 않음
  • createRef(클래스 컴포넌트용)과 다르게, useRef는 렌더 간에도 같은 객체를 유지함

 

실제 본인 예시 - 버튼 '따닥 방지'

 

10년차 시니어 개발자 분이 '수연님 이거 api 요청 2번 이상 들어오는 것 같은데 버튼 따닥 방지 해주세요'라고 요청하심

 

[1] 내가 useState로 시도한 방법

const [buttonClicked, setButtonClicked] = useState(false);

<button
  onClick={() => {
    if (buttonClicked) return; // 두 번째 클릭 막으려 함
    setButtonClicked(true);
    // 버튼 클릭 로직 실행
  }}
/>

 

겉보기엔 'true로 바뀌니까 두 번째 클릭은 막히겠지'라고 생각함

하지만 React의 상태 업데이트는 비동기적이기 때문에 타이밍상 문제가 생김

 

즉, setButtonClicked(true)를 호출해도

그 순간에는 여전히 buttonClicked === false인 상태로 남아있음 (렌더 전이기 때문)

 

사용자가 빠르게 두 번 클릭하면

1. 첫 번째 클릭 -> setButton Clicked(true) 호출 (렌더는 아직 안 됨)

2. 두 번째 클릭 -> 여전히 이전 값(false)을 보고 있음 -> 또 실행 됨

3. 두 번 실행돼서 따닥 방지 안됨 (=> 혼남 ㅇㅇ)

 

렌더링 사이 짧은 틈 때문에 useState는 즉시 반응하지 못함

 

[2] useRef는 즉시 반응하는 이유

그리고 시니어 개발자분이 알려주신 방법

const isClickedRef = useRef(false);

<button
  onClick={() => {
    if (isClickedRef.current) return; // 이미 클릭된 상태면 return
    isClickedRef.current = true;
    // 버튼 클릭 로직 실행
  }}
/>

 

isClickedRef.current를 바꾸면 즉시 바뀜

왜냐면 useRef는 렌더링과 관계 없이 '그냥 변수처럼' 값이 바뀌기 때문

렌더를 기다릴 필요도 없고, 비동기도 아니고, 값이 바뀌어도 화면은 그대로임

 


useRef는 리렌더링 없이 즉시 기억함

useState는 리렌더링 후에야 반영되는 상태값

 

즉시 반응이 필요하면 useRef 사용

 


출처

[1] 매일메일 251006 useRef는 언제 사용하나요? https://www.maeil-mail.kr/question/126

[2] react 공식 문서 useRef https://ko.react.dev/reference/react/useRef

[3] 1,2번을 gpt에 넣고 설명해달라고 함

[4] 킹갓재너럴개발의 신 종근님의 버튼 따닥 로직

728x90