Search

useRef

useRef()를 사용하면 바닐라 자바스크립트에서 document.querySelector()와 같은 함수를 사용해 DOM 요소를 직접 사용하는 것처럼 리액트 컴포넌트에서 DOM 요소에 직접 접근할 수 있습니다. 이로 인해 리액트에서 <input>과 같은 DOM 요소를 focus하거나 크기나 위치 값을 불러오는 것 등의 DOM 관련 처리가 보다 용이해집니다.
useRef()로 생성한 참조(reference) 값은 컴포넌트가 마운트된 이후 언마운트 될때까지의 생명주기(lifecycle)동안 유지됩니다. 상태(state)와는 다르게 값이 변경되더라도 컴포넌트가 리랜더링을 수행하지 않으며, 값이 변경된 상태에서 리랜더링이 되더라도 변경된 값을 그대로 유지합니다. 일반 변수(varlet)의 경우에는 컴포넌트가 리랜더링될 때마다 매번 변경된 값을 잃고 초기화되게 됩니다. 이러한 특징으로 인해 변하는 값을 저장해야 하지만 컴포넌트의 리랜더링이 필요하지 않은 경우와 컴포넌트가 리랜더링되더라도 값을 잃지 않아야 하는 경우에 유용하게 사용할 수 있습니다.

리액트 컴포넌트에서 useRef를 사용하기에 유용한 경우

1.
<input>, <video>와 같은 DOM 요소에 접근할 때 ( 가장 많이 사용됩니다)
2.
Websoket과 같이 리랜더링 되더라도 지속되어야하는 인스턴스 객체 저장
3.
setTimeout(), setInterval() 타임 함수의 timeoutID 관리
4.
기타 화면에 즉각 반영할 필요가 없는 변하는 값을 저장할 때

useRef의 사용

useRef() 함수를 실행하면 ref 객체가 생성됩니다. 함수 인자에 값을 지정하면 그 값이 ref 객체의 초기 값이 됩니다.
const ref = useRef(/* 초기 값 */);
JavaScript
복사
ref 객체는 current에 값이 저장되어 있습니다. 값을 읽거나 변경할때 ref.current 형태로 사용합니다.
const ref = useRef(0); console.log(ref.current); // 0 ref.current = 1; console.log(ref.current); // 1
JavaScript
복사

DOM 요소를 직접 사용

리액트 컴포넌트에서 DOM 요소에 직접 접근하기 위해서는 useRef()로 생성한 객체를 요소의 ref 속성으로 지정해주면 됩니다.
import { useRef } from 'react'; const App = () => { const ref = useRef(); return <input ref={ref} />; };
JavaScript
복사
이후 ref 객체의 current에서 DOM 요소를 컨트롤할 수 있습니다.
const App = () => { const ref = useRef(); // ref로 지정된 input 요소를 focus 상태로 const onClick = () => ref.current.focus(); return ( <> <input ref={ref} /> <button onClick={onClick}> focus </button> </> ); };
JavaScript
복사
ref 객체로 지정한 요소는 컴포넌트가 마운트 된 이후 시점부터 핸들링 할 수 있습니다. 마운트 되기 이전에는 아직 DOM 트리에 요소가 생성되지 않았으므로 ref.current 값에 요소가 없는 상태입니다. 만약 아래 코드처럼 마운트 이전 상황에서 요소의 메소드를 실행하려고 하면 에러가 발생합니다.
import { useEffect, useRef } from 'react'; const App = () => { const ref = useRef(); // 🙅🏻‍♂️ 컴포넌트가 아직 마운트되지 않았으므로 DOM 요소가 current에 존재하지 않습니다. ref.current.focus(); return <input ref={ref} />; };
JavaScript
복사
컴포넌트가 마운트된 이후에 자동으로 요소의 메소드를 실행할 수 있게 하려면 아래 코드처럼 useEffect()에 포함시켜서 사용하면 됩니다.
import { useEffect, useRef } from 'react'; const App = () => { const ref = useRef(); // 🙆🏻‍♂️ useEffect의 사이드 이펙트는 컴포넌트가 마운트된 이후에만 실행되므로 current에 요소가 포함되어 있습니다. useEffect(() => { ref.current.focus(); }, []); return <input ref={ref} />; };
JavaScript
복사
컴포넌트의 마운트(Mount) 및 언마운트(Unmount)와 useEffect()에 대해 잘 모르신다면 React Hooks useEffect 편을 참고하세요.

useRef를 사용해 input의 value 값 사용

<input>에 입력된 value 값을 리액트 컴포넌트에서 사용하려고 할때, 아래 코드처럼 useState()로 상태를 만든 뒤 <input>에 입력된 값이 변경될 때마다 상태 값을 value 값으로 업데이트하여 사용하는 식을 많이 사용합니다. 하지만 이 방법은 입력 값이 변경될 때마다 매번 상태 값을 변경하게 되고 그 때마다 리랜더링이 발생하게 됩니다.
const App = () => { const [inputValue, setInputValue] = useState(''); // input의 onChange 이벤트가 발생할때 inputValue 값을 변경된 value 값으로 업데이트 const onChange = (e) => setInputValue(e.target.value); // 버튼 클릭했을때 inputValue 상태 값을 알림 const onClick = () => alert(inputValue); return ( <> <input value={inputValue} onChange={onChange} /> <button onClick={onClick}> 입력된 값 확인 </button> </> ); };
JavaScript
복사
useState로 input의 value 값을 사용하려고 하면 매번 입력될때마다 상태도 업데이트 해야하는 추가적인 작업이 수행됩니다.
useRef()를 사용해 <input> 요소의 value 값을 직접 읽어서 사용하면 작성하는 코드도 적어지고 상태 업데이트로 발생하는 불필요한 리랜더링 작업도 수행되지 않습니다.
const App = () => { const inputRef = useRef(); // 버튼 클릭했을때 ref에 담긴 요소의 value 값을 알림 const onClick = () => alert(inputRef.current.value); return ( <> <input ref={inputRef} /> <button onClick={onClick}> 입력된 값 확인 </button> </> ); };
JavaScript
복사
useRef로 DOM 요소에 직접 접근해서 value 값을 가져오면 상태를 업데이트하는 작업이 생략됩니다.
useRef()를 활용해서 <input>value를 얻는 방법이 리랜더링 방지로 얻는 약간의 성능적인 장점이 있지만 그렇게까지 큰 유의미한 차이가 있지는 않습니다. 상태와 onChange를 활용한 방법을 써야하는 경우(값의 즉각적인 유효성 검사나 검색 수행)도 있기 때문에 상황에 따라 적합한 방법을 사용하면 됩니다.

하위 컴포넌트에 ref를 넘겨주는 경우

리액트 컴포넌트는 “ref”라는 명칭의 props를 지정할 수 없게 되어있기 때문에 아래처럼 하위 컴포넌트에 ref를 지정해서 사용하려고 하면 에러가 발생합니다.
import { useRef } from 'react'; const App = () => { const inputRef = useRef(); const onClick = () => alert(inputRef.current.value); return ( <> <InputComponent ref={inputRef} /> <button onClick={onClick}> 입력된 값 확인 </button> </> ); }; // 🙅🏻‍♂️ 리액트 컴포넌트에서 ref는 props로 사용할 수 없는 값이므로 ref가 전달되지 않습니다. const InputComponent = ({ ref }) => { return <input ref={ref} /> }
JavaScript
복사
“ref” 명칭 되신 다른 명칭을 컴포넌트에 지정해 사용하게 되면(예: “componentRef”) 에러가 발생하지는 않지만 DOM 요소에 ref를 적용할때와 다른 명칭을 사용하게 되므로 통일되지 않은 어색한 코드가 된 느낌을 받게 됩니다.
넘겨 받는 하위 컴포넌트에서 ref를 지정 받기 위해서는 리액트 forwardRef() 함수로 생성한 컴포넌트여야 합니다. 사용 방법은 forwardRef()의 인자로 기존 함수형 컴포넌트를 포함시켜 주면 됩니다. 이 때 두번째 인자로 ref 값을 받으므로 이 ref 값을 컴포넌트 내부에서 사용하면 됩니다.
import { useRef, forwardRef } from 'react'; const App = () => { const inputRef = useRef(); const onClick = () => alert(inputRef.current.value); return ( <> <InputComponent ref={inputRef} /> <button onClick={onClick}> 입력된 값 확인 </button> </> ); }; // 🙆🏻‍♂️ forwardRef 함수로 래핑된 컴포넌트는 두번째 인자로 ref 값을 정상적으로 전달 받습니다. const InputComponent = forwardRef((props, ref) => { return <input ref={ref} /> });
JavaScript
복사

타임 함수의 타임아웃 ID 관리

useRef()로 생성한 참조 값은 컴포넌트가 아무리 리랜더링 되더라도 본래의 값을 잃는 초기화가 발생하지 않으며, 값을 수정하더라도 컴포넌트 리랜더링이 발생하지 않습니다. 이러한 특징은 컴포넌트에 랜더링에 영향을 주지 않으면서 값을 온전히 가지고 있어야 하는 경우에 유용합니다. setTimeout(), setInterval()과 같이 백그라운드에서 실행되며, 취소(clearTimeout())하기 위해 timeoutID 값을 온전히 가지고 있어야 하는 경우가 해당됩니다.
아래는 이를 이해하기 위한 간단한 카운트다운 앱 코드입니다. 초기 값 10의 상태가 setInterval() 함수로 1초마다 1씩 감소하게 되어있고, 중지 버튼을 클릭할 경우 ref 객체에 보관된 timeoutID로 clearTimeout()을 실행해 타임 interval을 제거합니다. 시작 버튼을 클릭할 경우 ref 객체에 다시 setInterval()을 실행해서 신규 timeoutID를 저장합니다.
import { useState, useEffect, useRef } from 'react'; import ReactDOM from 'react-dom/client'; // Countdown 컴포넌트 const Countdown = () => { const [count, setCount] = useState(10); const [isPaused, setIsPaused] = useState(false); const counterRef = useRef(); const decrease = () => setCount((current) => current - 1); // 카운트다운 중지 또는 시작하는 함수 const pauseOrRun = () => { // 카운트다운이 0에 도달해서 중지했을때 10으로 재시작 if (isPaused && count <= 0) { setCount(10); } if (isPaused) { // 중지일때 ref에 setInterval의 timeoutID 저장해 카운트다운 시작 counterRef.current = setInterval(decrease, 1000); } else { // 시작일때 ref의 timeoutID로 clearTimeout 실행해서 카운트다운 중지 clearTimeout(counterRef.current); } setIsPaused(currentIsPaused => !currentIsPaused); } // 컴포넌트가 마운트될때 카운트다운 실행 useEffect(() => { // setInterval로 카운트다운을 시작하고 ref에 timeoutID 저장 counterRef.current = setInterval(decrease, 1000); // 언마운트될때 clearTimeout으로 정리 return () => clearTimeout(counterRef.current); }, []); // count가 0에 도달하면 카운트다운 중지 useEffect(() => { if (count <= 0) { clearTimeout(counterRef.current); setIsPaused(true); } }, [count]); return ( <> <h1>{count}</h1> <button onClick={pauseOrRun}>{isPaused ? '시작' : '중지'}</button> </> ); } const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<Countdown />);
JavaScript
복사

 useContext

 useMemo

React Hooks 목록