useRef
는 렌더링을 할 필요가 없는 값을 참조할 수 있게 해주는 리액트 훅이에요.
const ref = useRef(initialValue)
Reference | 레퍼런스
useRef(initialValue)
ref를 선언하고 싶다면 최상위 컴포넌트에서 useRef
를 호출하세요.
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
}
Parameters | 파라미터
initialValue
: ref 객체의current
속성이 최초로 지정될 값. 타입은 상관이 없어요. 이 인자는 최초 렌더링 이후에는 무시돼요.
Returns | 반환값
useRef
는 단일 속성을 가지는 객체를 반환해요.
current
: 맨 처음에는, 인자로 전달한initialValue
로 설정돼요. 나중에 다른 값으로 설정할 수 있어요. 만약 JSX 노드에ref
속성을 전달하는 것으로 리액트에 ref 객체를 전달한다면 리액트는current
속성을 지정해요.
이후 렌더링에서 useRef
는 같은 객체를 반환해요.
Cavest | 주의 사항
ref.current
속성을 변경할 수 있어요. 상태와 다르게 ref는 변경할 수 있어요. 그러나 (상태의 일부와 같이) 렌더링에 사용되는 객체를 갖고 있다면 객체를 변경하면 안돼요.ref.current
속성을 변경할 때, 리액트는 컴포넌트를 리렌더링 하지 않아요. ref는 자바스크립트 일반 객체이기 때문에 이 속성을 변경하는 것을 리액트는 알지 못해요.ref.current
는 초기화 할 때를 제외하고 쓰거나 읽지 마세요. 이는 컴포넌트가 예상하지 못한 동작을 하게 만들어요.- 엄격한 모드(Strict Mode)에서 리액트는 의도하지 않은 불순물을 찾는 것을 돕기 위해 컴포넌트 함수를 두 번 호출해요. 개발 모드에서만 이렇게 동작하고 실제 프로덕션 환경에는 영향을 미치지 않아요. 각각의 ref 객체는 두 번 생성되지만 이 버전들 중 하나는 버려져요. 만약 컴포넌트 함수가 순수함수라면 이 동작이 로직에 영향을 미치지 않을 거예요.
Usage | 용법
Referencing a value with ref | ref로 값 참조하기
하나 또는 그 이상의 ref를 선언하고 싶다면 최상위 컴포넌트에서 useRef
를 호출하세요.
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
}
useRef
는 전달받은 초기 값으로 처음에 지정된 current
라는 단일 속성을 가진 ref 객체를 반환해요.
이후 렌더링에서 useRef
는 같은 객체를 반환해요. 정보를 저장하고 나중에 이 정보를 읽기 위하여 current 속성을 변경할 수 있어요. 이는 상태를 상기시키지만 중요한 차이점이 있어요.
ref를 바꾸는 것은 리렌더링을 발생시키지 않아요. 이는 ref가 컴포넌트의 시각적인 결과물에 영향을 미치지 않아 정보를 저장하기에 완벽하다는 것을 의미해요. 예를 들어 만약 인터벌 ID를 저장하고 나중에 찾아야한다면 ref에 넣을 수 있어요. ref 안에서 값을 업데이트하기 위해서는 ref의 current 속성을 직접 변경해야해요.
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
나중에 ref에서 인터벌 ID를 읽을 수 있으므로 인터벌을 삭제할 수 있어요.
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
ref를 사용하면 다음이 보장돼요.
- (모든 렌더링마다 삭제되는 일반적인 변수와는 달리) 리렌더링 사이에도 정보를 저장할 수 있어요.
- (리렌더링을 발생시키는 상태 변수와는 달리) ref를 수정하는 것은 리렌더링을 발생시키지 않아요.
- (공유가 되는 외부에서 선언된 변수와는 달리) 컴포넌트의 복사본에 정보는 지역적으로 유지돼요.
ref를 변경하는 것은 리렌더링을 발생시키지 않으며 ref는 화면에 보여주고 싶은 정보를 저장하는데는 적합하지 않아요. 그럴 때는 상태를 사용하세요. useRef
와 useState
중 선택하기 문서를 더 읽어보세요.
useRef로 값을 참조하는 예시
1. 카운터 클릭하기
이 컴포넌트는 버튼이 몇 번 클릭되는지를 추적하기 위한 ref를 갖고 있어요. 클릭하는 횟수는 이벤트 핸들러에서만 읽히고 작성되기 때문에 이곳에서는 상태 대신 ref를 사용해도 괜찮다는 점을 알아두세요.
만약 {ref.current}
를 JSX에서 보여준다면, 클릭을 해도 숫자가 업데이트 되지 않을 거예요. 이는 ref.current
를 설정하는 것은 리렌더링을 발생시키지 않기 때문이에요. 렌더링에 사용되는 정보는 ref가 아닌 상태가 되어야해요.
2. 스톱워치
이 예시는 상태와 ref를 혼합하여 사용하고 있어요. 렌더링에 사용되는 startTime
와 now
는 모두 상태변수예요. 그러나 인터벌 ID를 갖고 있어야 버튼을 누르면 인터벌을 멈출 수 있어요. 인터벌 ID가 렌더링에 사용되지 않기 때문에 이 변수는 ref에 넣고 손수 업데이트 시켜주는 것이 적합한 방법이에요.
함정
렌더링을 하는 동안ref.current
를 쓰거나 읽지 마세요.
리액트는 컴포넌트 바디가 순수 함수처럼 동작할 것이라고 예상해요.
- 만약 입력(props, 상태 그리고 컨텍스트)이 똑같다면 정확히 같은 JSX를 반환해요.
- 다른 순서로 혹은 다른 인자를 갖고ref.current
를 호출하는 것은 다른 호출의 결과에 영향을 미치지 않아야 해요.
렌더링 동안 ref를 읽거나 쓰는 것은 이러한 예상을 깨뜨려요.
function MyComponent() { // ... // 🚩렌더링하는 동안 ref에 작성하지 마세요. myRef.current = 123; // ... // 🚩 렌더링하는 동안 ref를 읽지 마세요. return <h1>{myOtherRef.current}</h1>; }
대신에 이벤트 핸들러나 이펙트에서 ref를 읽거나 쓸 수 있어요.
function MyComponent() { // ... useEffect(() => { // ✅ 이펙트 안에서는 ref를 읽고 쓸 수 있어요.. myRef.current = 123; }); // ... function handleClick() { // ✅ 이벤트 핸들러 안에서는 ref를 읽고 쓸 수 있어요.. doSomething(myOtherRef.current); } // ... }
만약 렌더링을 하는 동안 읽거나 써야한다면 상태를 사용하세요.
만약 이 규칙을 어긴다고 해도 컴포넌트는 동작해요. 그러나 리액트에 추가하려는 대부분의 새로운 특성은 이러한 예상에 의존하고 있어요. 컴포넌트를 순수하게 유지하기 문서를 읽어보세요.
Manipulating the DOM with ref | ref로 DOM 조작하기
ref를 사용해서 DOM을 조작하는 것은 특히나 흔해요. 리액트는 이를 위하여 내장 지원을 하고 있어요.
먼저 초기값을 null
로 가지는 ref 객체를 선언하세요.
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
}
그리고 나서 ref
속성을 조작하고 싶은 DOM 노드의 JSX에 ref 객체를 전달하세요.
// ...
return <input ref={inputRef} />;
리액트가 DOM 노드를 만들고 화면에 넣은 후에 리액트는 ref 객체의 current 속성을 해당 DOM 노드로 설정해요. 이제 <input>
의 DOM 노드에 접근하고 focus()
와 같은 메서드를 호출할 수 있어요.
function handleClick() {
inputRef.current.focus();
}
노드가 화면에서 사라지면 current
속성은 다시 null
로 설정돼요.
ref로 DOM 조작하기에 대해 더 알아보세요.
useRef로 DOM 조작하기 예시
1. 텍스트 입력창 포커싱 하기
이 예시에서 버튼을 클릭하면 입력창에 포커스가 돼요.
2. 뷰에 이미지 스크롤링하기
이 예시에서 버튼을 누르면 뷰에 이미지가 스크롤링 돼요. 리스트 DOM 노드에 ref를 사용하고 스크롤 하고 싶은 이미지를 찾기 위해 DOM의 querySelectorAll
API를 호출했어요.
3. 영상 재생 및 일시정지하기
이 예시에서 play()
와 pause()
를 <video>
DOM 노드에서 호출하기 위해 ref를 사용했어요.
4. 컴포넌트에 ref 노출하기
때때로 부모 컴포넌트가 컴포넌트 내부에서 DOM을 조작하게 만들고 싶을 수 있어요. 예를 들어 MyInput
컴포넌트를 작성했지만 부모 컴포넌트가 (부모가 접근할 수 없는) 입력창을 포커싱할 수 있도록 만들고 싶을 수도 있어요. 입력창을 갖고 있는 useRef
와 입력창을 부모 컴포넌트에 노출시켜주는 forwardRef
를 혼합하여 사용할 수 있어요. 이에 대한 자세한 설명을 읽어보세요.
Avoiding recreating the ref contents | ref 콘텐츠가 재생성하는 것을 피하기
리액트는 처음에 초기 ref 값을 저장하고 다음 렌더링에서 이를 무시해요.
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
}
new VideoPlayer()
의 결과가 초기 렌더링에만 사용됨에도 불구하고 모든 렌더링에서 이 함수를 여전히 호출해요. 만약 고비용의 객체를 생성한다면 이는 낭비적이에요.
이를 해결하기 위해서는 아래와 같이 ref를 초기화하세요.
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
}
일반적으로 렌더링 하는 동안 ref.current
를 작성하거나 읽는 것은 허용되지 않아요. 그러나 결과가 언제나 같고 오직 초기화 되는 동안 실행된다는 조건으로 인해 완벽하게 예상이 가능하므로 이런 경우에는 허용돼요.
useRef를 나중에 초기화할 때 null 체크를 피하는 방법
만약 타입 체커를 사용하고 항상 null
을 확인하고 싶은 것이 아니라면 아래와 같은 패턴을 시도할 수 있어요.
function Video() {
const playerRef = useRef(null);
function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}
// ...
}
여기서 useRef
자체는 null을 사용할 수 있어요. (nullable해요.) 그러나 getPlayer()
가 null
을 반환하는 경우는 없다는 것을 타입 체커에게 확신시킬 수 있어요. 그리고 나서 getPlayer()
를 이벤트 핸들러에서 사용하세요.
Troubleshooting | 트러블슈팅
I can’t get a ref to a custom component | 커스텀한 컴포넌트에 대한 ref를 얻을 수 없어요.
만약 ref
를 아래와 같이 직접 만든 컴포넌트에 전달하고 싶다면:
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
콘솔에서 에러를 얻을 거예요.
콘솔
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
경고: 함수 컴포넌트는 ref를 받을 수 없어요. 이 ref에 접근하는 시도는 실패할 거예요. React.forwardRef()를 사용하려고 했나요?
기본적으로 컴포넌트 내부에서 DOM 노드에 ref를 노출하지 않아요.
이를 고치기 위해서는 ref를 얻고 싶은 컴포넌트를 찾으세요.
export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}
그리고 나서 아래와 같이 forwardRef
에서 이 컴포넌트를 감싸세요.
import { forwardRef } from 'react';
const MyInput = forwardRef(({ value, onChange }, ref) => {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
});
export default MyInput;
이제 부모 컴포넌트는 ref를 얻을 수 있어요.
다른 컴포넌트의 DOM 노드에 접근하기 문서에서 이에 대해 더 알아보세요.
'리액트 공식문서 | React Docs > Reference > react@18.2.0' 카테고리의 다른 글
[Hooks] useSyncExternalStore | useSyncExternalStore 훅 (1) | 2024.02.07 |
---|---|
[Hooks] useState | useState 훅 (1) | 2024.02.06 |
[Hooks] useReducer | useReducer 훅 (1) | 2024.02.04 |
[Hooks] useOptimistic | useOptimistic 훅 (0) | 2024.02.03 |
[Hooks] useMemo | useMemo 훅 (0) | 2024.02.01 |