해당 내용은 2023년 01월 28일 React Docs BETA 버전을 토대로 작성되었습니다. - 링크
✅ useRef란?
렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook이다. 여기서 Ref는 reference, 즉 참조를 뜻한다. 값을 참조하고 DOM을 조작하는 데에 주로 사용된다.
참조란? 객체의 실제 위치를 가리키는 포인터이다. 실제 값이 아닌 메모리 공간의 주소를 가진다.
const ref = useRef(initialValue)
매개변수
- initialValue: 참조하려는 ref 객체의 current property의 초기 값이다.
반환
단일 속성(current)을 가진 객체를 반환한다.
- current: 처음에는 매개변수로 받은 초기 값으로 설정되어 있고, ref 객체를 JSX 노드 속성으로 React에 전달하면 해당 노드 current 속성을 설정한다. 간단하게 말하자면, <div>에 ref를 설정하면 <div>가 current 속성으로 설정된다는 것이다.
주의 사항
- state와 달리 ref.current 속성을 변경할 수 있다. 하지만 렌더링에 사용되는 객체(예. 상태의 일부)를 보유하는 경우 해당 객체를 변경해서는 안 된다.
- ref.current 속성을 변경할 때, 컴포넌트가 리렌더링 되지는 않는다. ref는 일반 JavaScript 객체이기 때문에 React는 언제 변하는지 모른다.
- 초기화를 제외하고는 렌더링 중에 ref.current를 읽거나 쓰면 안 된다. 이로 인해 컴포넌트의 동작을 예측할 수 없게 된다.
- Strict Mode에서, 컴포넌트가 두 번 실행되는데 이것은 각 ref 객체가 두 번 생성되고 하나는 삭제된다. 그러므로 컴포넌트가 순수한 경우, 컴포넌트 로직에 영향을 미치지 않는다.
🤔사용해 보기
🟢 1. ref로 값 참조하기
위에서 이야기했듯이 useRef는 처음에 제공된 초기값으로 설정된 current 속성 하나만 가진 ref 객체를 반환한다.
그다음 렌더링에서, useRef는 같은 객체를 반환하며 current 속성을 변경하여 정보를 저장하고 나중에 읽을 수 있다. state와 비슷한 느낌이 들지만 중요한 차이는 ref를 변경해도 리렌더링이 발생하지 않는다. 즉, ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데에 적합하다. 이 부분의 설명이 어렵다면 코드를 보면서 이해하자.
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
위 컴포넌트는 ref를 사용하여 버튼 클릭 횟수를 추적한다. 클릭 수는 이벤트 헨들러에서만 읽고 쓰기 때문에 여기에서는 상태 대신 ref를 사용해도 괜찮다.
export default function StopWatch() {
// 렌더링에 사용되는 상태 변수
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current)
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
}
setInterval은 IntervalID를 반환하는데 해당 정보를 Ref에 담아주었다. 해당 정보를 가지고 동일한 함수의 동작을 중지할 수 있다. intervalRef.current가 IntervalID를 보유하고 있기에 clearInterval이 적절하게 실행될 수 있다.
- 리렌더링 사이에 정보를 저장할 수 있다. (렌더링 될 때마다 재설정되는 일반 변수와 달리)
- 변경해도 리렌더링 되지 않는다. (변경됨에 따라 리렌더링을 유발하는 상태 변수와 달리)
- 정보는 컴포넌트의 각 복사본에 로컬이다. (공유되는 외부 변수와 달리)
🟡 함정
렌더링 중에 ref.current를 쓰거나 읽지 마라. 항상 말해왔듯이 React 컴포넌트는 순수해야 한다.
- 입력(props, state 및 context)이 동일한 경우 정확히 동일한 JSX를 반환해야 한다.
- 다른 순서로 호출하거나 다른 인자로 호출해도 다른 호출 결과에 영향을 미쳐선 안된다.
function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}
대신에 이벤트나 Effect에서 ref를 읽거나 쓸 수 있다.
function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}
렌더링 중에 무언가를 읽거나 써야 하는 경우는 state를 사용해야 한다.
🟢 2. ref로 DOM 조작하기
먼저 아래와 같이 ref 객체를 선언하고 조작하려는 DOM 노드의 JSX에 속성으로 ref 객체를 전달한다.
const inputRef = useRef(null);
// ...
return <input ref={inputRef} />;
여기서 React는 DOM 노드를 생성하고 화면에 표시한 후 ref 객체의 current 속성을 해당 DOM 노드로 설정한다. 이렇게 하면 <input>의 DOM 노드에 접근할 수 있기에 focus()와 같은 메서드를 호출할 수 있다.
function handleClick() {
inputRef.current.focus();
}
React는 해당 노드가 화면에서 제거될 대 null로 current 속성을 다시 설정한다.
예시 1. 이미지를 스크롤하여 보기
export default function CatFriends() {
const listRef = useRef(null);
function scrollToIndex(index) {
const listNode = listRef.current;
// This line assumes a particular DOM structure:
const imgNode = listNode.querySelectorAll('li > img')[index];
imgNode.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
return (
<>
<nav>
<button onClick={() => scrollToIndex(0)}>
Tom
</button>
<button onClick={() => scrollToIndex(1)}>
Maru
</button>
<button onClick={() => scrollToIndex(2)}>
Jellylorum
</button>
</nav>
<div>
<ul ref={listRef}>
<li>
<img
src="https://placekitten.com/g/200/200"
alt="Tom"
/>
</li>
<li>
...
</li>
</ul>
</div>
</>
);
}
버튼을 클릭하면 해당 이미지로 스크롤된다. <li> DOM 노드에 대한 참조를 사용하고, DOM querySelectorAll API를 호출하여 스크롤하기를 원하는 이미지를 찾는다.
예시 2. 비디오 재생 및 일시 중지
export default function VideoPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
const ref = useRef(null);
function handleClick() {
const nextIsPlaying = !isPlaying;
setIsPlaying(nextIsPlaying);
if (nextIsPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}
return (
<>
<button onClick={handleClick}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<video
width="250"
ref={ref}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
type="video/mp4"
/>
</video>
</>
);
}
video DOM 노드에서 play()와 pause() 호출
🟢 ref content 재생성 방지하기
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
new VideoPlayer()의 결과는 초기 렌더링에서만 사용되지만, 이 함수는 모든 렌더링에서 호출되고 있다. 이 문제를 해결해 보자.
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
ref.current는 일반적으로 렌더링 중에 쓰거나 읽으면 안 되지만 이 경우에는 결과가 항상 같고, 해당 조건문은 초기화 중에만 실행되므로 완전히 예측할 수 있기에 괜찮다.
나중에 useRef를 초기화할 때 null check를 피하는 방법
type을 검사하고 항상 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;
}
playerRef 자체는 null을 허용하지만 getPlayer()는 null을 반환하지 않는다고 type checker에게 이야기하고 있다. 그런 다음 이벤트 헨들러에서 getPlayer()를 사용한다.
👉 다른 컴포넌트의 DOM 노드에 접근하기
'개발자의 공부 > React' 카테고리의 다른 글
이미지 최적화에 대한 명확한 가이드 (0) | 2023.02.01 |
---|---|
프론트엔드 최적화 시도 - 1 (0) | 2023.01.30 |
[React]Page Routing(v6.4) (0) | 2022.11.28 |
[React]Google 로그인 OAuth2로 다른 Google API와 연동 (0) | 2022.11.27 |
[React]useContext (0) | 2022.11.15 |
해당 내용은 2023년 01월 28일 React Docs BETA 버전을 토대로 작성되었습니다. - 링크
✅ useRef란?
렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook이다. 여기서 Ref는 reference, 즉 참조를 뜻한다. 값을 참조하고 DOM을 조작하는 데에 주로 사용된다.
참조란? 객체의 실제 위치를 가리키는 포인터이다. 실제 값이 아닌 메모리 공간의 주소를 가진다.
const ref = useRef(initialValue)
매개변수
- initialValue: 참조하려는 ref 객체의 current property의 초기 값이다.
반환
단일 속성(current)을 가진 객체를 반환한다.
- current: 처음에는 매개변수로 받은 초기 값으로 설정되어 있고, ref 객체를 JSX 노드 속성으로 React에 전달하면 해당 노드 current 속성을 설정한다. 간단하게 말하자면, <div>에 ref를 설정하면 <div>가 current 속성으로 설정된다는 것이다.
주의 사항
- state와 달리 ref.current 속성을 변경할 수 있다. 하지만 렌더링에 사용되는 객체(예. 상태의 일부)를 보유하는 경우 해당 객체를 변경해서는 안 된다.
- ref.current 속성을 변경할 때, 컴포넌트가 리렌더링 되지는 않는다. ref는 일반 JavaScript 객체이기 때문에 React는 언제 변하는지 모른다.
- 초기화를 제외하고는 렌더링 중에 ref.current를 읽거나 쓰면 안 된다. 이로 인해 컴포넌트의 동작을 예측할 수 없게 된다.
- Strict Mode에서, 컴포넌트가 두 번 실행되는데 이것은 각 ref 객체가 두 번 생성되고 하나는 삭제된다. 그러므로 컴포넌트가 순수한 경우, 컴포넌트 로직에 영향을 미치지 않는다.
🤔사용해 보기
🟢 1. ref로 값 참조하기
위에서 이야기했듯이 useRef는 처음에 제공된 초기값으로 설정된 current 속성 하나만 가진 ref 객체를 반환한다.
그다음 렌더링에서, useRef는 같은 객체를 반환하며 current 속성을 변경하여 정보를 저장하고 나중에 읽을 수 있다. state와 비슷한 느낌이 들지만 중요한 차이는 ref를 변경해도 리렌더링이 발생하지 않는다. 즉, ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데에 적합하다. 이 부분의 설명이 어렵다면 코드를 보면서 이해하자.
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
위 컴포넌트는 ref를 사용하여 버튼 클릭 횟수를 추적한다. 클릭 수는 이벤트 헨들러에서만 읽고 쓰기 때문에 여기에서는 상태 대신 ref를 사용해도 괜찮다.
export default function StopWatch() {
// 렌더링에 사용되는 상태 변수
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current)
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
}
setInterval은 IntervalID를 반환하는데 해당 정보를 Ref에 담아주었다. 해당 정보를 가지고 동일한 함수의 동작을 중지할 수 있다. intervalRef.current가 IntervalID를 보유하고 있기에 clearInterval이 적절하게 실행될 수 있다.
- 리렌더링 사이에 정보를 저장할 수 있다. (렌더링 될 때마다 재설정되는 일반 변수와 달리)
- 변경해도 리렌더링 되지 않는다. (변경됨에 따라 리렌더링을 유발하는 상태 변수와 달리)
- 정보는 컴포넌트의 각 복사본에 로컬이다. (공유되는 외부 변수와 달리)
🟡 함정
렌더링 중에 ref.current를 쓰거나 읽지 마라. 항상 말해왔듯이 React 컴포넌트는 순수해야 한다.
- 입력(props, state 및 context)이 동일한 경우 정확히 동일한 JSX를 반환해야 한다.
- 다른 순서로 호출하거나 다른 인자로 호출해도 다른 호출 결과에 영향을 미쳐선 안된다.
function MyComponent() {
// ...
// 🚩 Don't write a ref during rendering
myRef.current = 123;
// ...
// 🚩 Don't read a ref during rendering
return <h1>{myOtherRef.current}</h1>;
}
대신에 이벤트나 Effect에서 ref를 읽거나 쓸 수 있다.
function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}
렌더링 중에 무언가를 읽거나 써야 하는 경우는 state를 사용해야 한다.
🟢 2. ref로 DOM 조작하기
먼저 아래와 같이 ref 객체를 선언하고 조작하려는 DOM 노드의 JSX에 속성으로 ref 객체를 전달한다.
const inputRef = useRef(null);
// ...
return <input ref={inputRef} />;
여기서 React는 DOM 노드를 생성하고 화면에 표시한 후 ref 객체의 current 속성을 해당 DOM 노드로 설정한다. 이렇게 하면 <input>의 DOM 노드에 접근할 수 있기에 focus()와 같은 메서드를 호출할 수 있다.
function handleClick() {
inputRef.current.focus();
}
React는 해당 노드가 화면에서 제거될 대 null로 current 속성을 다시 설정한다.
예시 1. 이미지를 스크롤하여 보기
export default function CatFriends() {
const listRef = useRef(null);
function scrollToIndex(index) {
const listNode = listRef.current;
// This line assumes a particular DOM structure:
const imgNode = listNode.querySelectorAll('li > img')[index];
imgNode.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}
return (
<>
<nav>
<button onClick={() => scrollToIndex(0)}>
Tom
</button>
<button onClick={() => scrollToIndex(1)}>
Maru
</button>
<button onClick={() => scrollToIndex(2)}>
Jellylorum
</button>
</nav>
<div>
<ul ref={listRef}>
<li>
<img
src="https://placekitten.com/g/200/200"
alt="Tom"
/>
</li>
<li>
...
</li>
</ul>
</div>
</>
);
}
버튼을 클릭하면 해당 이미지로 스크롤된다. <li> DOM 노드에 대한 참조를 사용하고, DOM querySelectorAll API를 호출하여 스크롤하기를 원하는 이미지를 찾는다.
예시 2. 비디오 재생 및 일시 중지
export default function VideoPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
const ref = useRef(null);
function handleClick() {
const nextIsPlaying = !isPlaying;
setIsPlaying(nextIsPlaying);
if (nextIsPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}
return (
<>
<button onClick={handleClick}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<video
width="250"
ref={ref}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
type="video/mp4"
/>
</video>
</>
);
}
video DOM 노드에서 play()와 pause() 호출
🟢 ref content 재생성 방지하기
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
new VideoPlayer()의 결과는 초기 렌더링에서만 사용되지만, 이 함수는 모든 렌더링에서 호출되고 있다. 이 문제를 해결해 보자.
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
ref.current는 일반적으로 렌더링 중에 쓰거나 읽으면 안 되지만 이 경우에는 결과가 항상 같고, 해당 조건문은 초기화 중에만 실행되므로 완전히 예측할 수 있기에 괜찮다.
나중에 useRef를 초기화할 때 null check를 피하는 방법
type을 검사하고 항상 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;
}
playerRef 자체는 null을 허용하지만 getPlayer()는 null을 반환하지 않는다고 type checker에게 이야기하고 있다. 그런 다음 이벤트 헨들러에서 getPlayer()를 사용한다.
👉 다른 컴포넌트의 DOM 노드에 접근하기
'개발자의 공부 > React' 카테고리의 다른 글
이미지 최적화에 대한 명확한 가이드 (0) | 2023.02.01 |
---|---|
프론트엔드 최적화 시도 - 1 (0) | 2023.01.30 |
[React]Page Routing(v6.4) (0) | 2022.11.28 |
[React]Google 로그인 OAuth2로 다른 Google API와 연동 (0) | 2022.11.27 |
[React]useContext (0) | 2022.11.15 |