useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 훅이다.
const ref = useRef(initialValue)
컴포넌트의 최상위 레벨에서 useRef를 호출하여 ref를 선언한다.
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
매개변수
useRef는 단일 프로퍼티를 가진 객체를 반환한다.
current : 처음에는 전달한 initialValue로 설정된다. 나중에 다른 값으로 바꿀 수 있고 ref객체를 JSX노드의 ref속성으로 리액트에 전달하면 리액트는 current 프로퍼티를 설정한다.
다음 렌더링에서 useRef는 동일한 객체를 반환한다.
주의사항
- ref.current 프로퍼티는 state와 달리 변이할 수 있다. 그러나 렌더링에 사용되는 객체(ex. state의 일부)를 포함하는 경우 해당 객체를 변이해서는 안된다.
- ref.current 프로퍼티를 변경해도 리액트는 컴포넌트를 다시 렌더링하지 않는다. ref는 일반 JavaScript 객체이기 때문에 리액트는 사용자가 언제 변경했는지 알지 못한다.
- 초기화를 제외하고는 렌더링 중에 ref.current를 쓰거나 읽지 말아야 한다. 이렇게 하면 컴포넌트의 동작을 예측할 수 없다.
- Stict Mode에서 리액트는 컴포넌트 함수를 두 번 호출하여 의도하지 않은 불순물을 찾을 수 있도록 돕는다. 이는 개발 환경 전용 동작이며 상용 환경에는 영향을 미치지 않는다. 각 ref 객체는 두번 생성되고 그 중 하나는 버려진다. 컴포넌트 함수는 순수해야 하고 컴포넌트의 로직에 영향을 미치지 않는다.
사용법
컴포넌트의 최상위 레벨에서 useRef를 호출하여 하나 이상의 ref를 선언한다.
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
useRef는 처음에 제공한 초기값으로 설정된 단일 current 프로퍼티가 있는 ref객체를 반환한다.
다음 렌더링에서 useRef는 동일한 객체를 반환한다. 정보를 저장하고 나중에 읽을 수 있도록 current 속성을 변경할 수 있다. state가 떠오를 수 있지만, 둘 사이는 중요한 차이점이 있다.
ref를 변경해도 리렌더링을 촉발하지 않는다. 즉 ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는데 적합하다. 예를 들어, interval ID를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있다. ref내부의 값을 업데이트 하려면 current 프로퍼티를 수동으로 변경해야 한다.
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
나중에 ref에서 해당 interval ID를 읽어 해당 interval을 취소할 수 있다.
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
ref를 사용하면 다음을 보장한다.
- (렌더링할 때마다 재설정되는 일반 변수와 달리) 리렌더링 사이에 정보를 저장할 수 있다.
- (리렌더링을 촉발하는 state변수와 달리) 변경해도 리렌더링을 촉발하지 않는다.
- (정보가 공유되는 외부 변수와 달리) 각각의 컴포넌트에 로컬로 저장된다.
ref를 변경해도 다시 렌더링되지 않으므로 화면에 표시되는 정보를 저장하는 데는 ref가 적합하지 않다. 대신 state를 사용하면 된다.
렌더링 중에는 ref.current를 쓰거나 읽지 마세요.
리액트는 컴포넌트의 본문이 순수 함수처럼 동작하기를 기대한다.
- 입력값들(props, state, context)이 동일하면 완전히 동일한 JSX를 반환해야 한다.
- 다른 순서나 다른 인수를 사용하여 호출해도 다른 호출의 결과에 영향을 미치지 않아야 한다.
렌더링 중에 ref를 읽거나 쓰면 이런 예측이 깨진다.
function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
// 🚩 렌더링 중에 ref를 작성하지 마세요.
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
// 🚩 렌더링 중에 ref를 읽지 마세요.
return <h1>{myOtherRef.current}</h1>;
}
대신 이벤트 핸들러나 Effect에서 ref를 읽거나 쓸 수 있다.
function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
// ✅ Effect에서 ref를 읽거나 쓸 수 있습니다.
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
// ✅ 이벤트 핸들러에서 ref를 읽거나 쓸 수 있습니다.
doSomething(myOtherRef.current);
}
// ...
}
렌더링 중에 무언가를 읽거나 써야만 하는 경우, state를 사용해야한다.
컴포넌트는 이러한 규칙을 어기더라도 여전히 작동할 순 있지만 리액트에 추가되는 대부분의 새로운 기능들은 이러한 기대에 의존한다.
ref로 DOM 조작하기
ref를 사용하여 DOM을 조작하는 것은 일반적이다. 리액트에는 이를 위한 기본 지원이 있다.
먼저 초기값이 null인 ref객체를 선언해야한다.
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
그런 다음 ref 객체를 ref속성으로 조작하려는 DOM노드의 JSX에 전달해야 한다.
// ...
return <input ref={inputRef} />;
리액트가 DOM노드를 생성하고 화면에 그린 후, React는 ref객체의 current 프로퍼티를 DOM노드로 설정한다.
이제 DOM노드 <input> 접근해 focus()와 같은 메서드를 호출할 수 있다.
function handleClick() {
inputRef.current.focus();
}
노드가 화면에서 제거되면 리액트는 current 프로퍼티를 다시 null로 설정한다.
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를 쓰거나 읽는 것은 허용되지 않는다. 하지만 이 경우에는 결과가 항상 동일하고 초기화 중에만 조건이 실행되므로 충분히 예측할 수 있다.
'React-study > dil' 카테고리의 다른 글
[DIL] useLayoutEffect (0) | 2024.06.11 |
---|---|
[DIL] useEffect (0) | 2024.06.04 |
[DIL] useContext (0) | 2024.05.29 |
[DIL] useReducer(2) (0) | 2024.05.28 |
[DIL] useReducer(1) (0) | 2024.05.27 |