< 목차 >
0. TL;DR
1. ref 사용하는 법
2. ref와 state의 차이
3. refs를 언제 사용해야 할까?
Ref로 값 참조하기 (Referencing Values with Refs)
0. TL;DR
- 컴포넌트가 일부 정보를 “기억”하게 하고 싶지만, 해당 정보가 렌더링을 유발하지 않도록 하고 싶을 때 Ref를 사용하자!
- ref는 읽거나 변경할 수 있는 current라는 프로퍼티를 호출할 수 있는 자바스크립트 순수객체
- ref를 변경하더라도, 리렌더링 되지 않는다.
- 렌더링 중에 ref.current를 읽거나 변경하지 말자. 건들면 컴포넌트를 예측하기 어렵게 된다.
1. ref 사용하는 법
// (1) useRef를 import 해서 ref 추가
import { useRef } from 'react';
export default function Counter() {
// (2) useRef Hook을 호출하고 참조할 초깃값을 유일 인자로 전달!
// 다음은 값 0에 대한 ref
let ref = useRef(0);
// [ref의 추상화] 여기서, useRef는 다음과 같은 객체를 반환함
// {
// current: 0 // useRef에 전달한 값
// }
function handleClick() {
// (3) `ref.current` 프로퍼티를 통해 해당 ref의 current 값에 접근할 수 있음
// 이 값은 의도적으로 변경할 수 있으므로 읽고 쓸 수 있음. React가 ‘추적하지 않는’ 구성 요소임
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
ref는 state처럼 문자열, 객체, 심지어 함수 등 모든 것을 가리킬 수 있다. state와 달리 ref는 ‘읽고 수정할 수 있는’ current 프로퍼티를 가진 일반 자바스크립트 객체다.
ref를 변경하더라도, 리렌더링 되지 않는다.
[예시] 스톱워치 구현하기
ref와 state를 단일 컴포넌트로 결합해 보자!
사용자가 ‘시작’을 누른 후 시간이 얼마나 지났는지 표시하려면,
- 시작 버튼을 누른 시기와
- 현재 시각을 추적해야 한다.
⇒ 이 정보는 렌더링에 사용되므로 state를 유지한다.
렌더링에 정보를 사용할 때, 해당 정보를 state로 유지한다.
import { useState, useRef } from 'react';
export default function Stopwatch() {
// (1) 시작 버튼을 누른 시기
const [startTime, setStartTime] = useState(null);\
// (2) 현재 시각 추적
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() {
// `Stop` 버튼을 누르면 now state 변수의 업데이트를 중지하기 위해 기존 interval을 취소해야 함
// → 이를 위해 clearInterval을 호출
// → 이전에 사용자가 시작을 눌렀을 때 setInterval 호출로 반환된 interval ID를 제공해야 하니까
// interval ID는 어딘가에 보관해야 함
// → interval ID는 렌더링에 사용되지 않으므로 ref에 저장할 수 있음
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>
</>
);
}
event handler에게만 필요한 정보이고 변경이 일어날 때 리렌더링이 필요하지 않다면, ref를 사용하는 것이 더 효율적일 수 있다.
2. ref와 state의 차이
💡 useRef는 내부적으로 어떻게 동작할까?
// React의 내부
function useRef(initialValue) {
// useRef는 첫 번째 렌더링 중에 { current: initialValue } 을 반환
// 여기서 state setter가 어떻게 사용되지 않는지 주의하자
// useRef는 항상 동일한 객체를 반환해야 하므로 필요하지 않음
const [ref, unused] = useState({ current: initialValue });
return ref;
}
// 만약, 개발자가 ref.current의 값을 변경할 경우엔 어떻게 될까?
// 아래와 같이 '즉시 변경'된다.
ref.current = 5;
console.log(ref.current); // 5
// 그 이유는 ref 자체가 자바스크립트 객체처럼 동작하기 때문
// 변형하는 객체가 렌더링에 사용되지 않으면 React는 ref 혹은 해당 콘텐츠를 어떻게 처리하든 신경 쓰지 않음
// React: "아 ㅋㅋ 렌더링될 때만 관여할 거라고"
3. refs를 언제 사용해야 할까?
(1) 일반적인 사용
: 컴포넌트가 React를 ‘외부’와 외부 API(컴포넌트의 형태에 영향을 미치지 않는 브라우저 API)와 통신해야 할 때 ref를 사용
(2) 특별한 상황에서의 사용
- timeout IDs를 저장
- DOM 엘리먼트 저장 및 조작
- JSX를 계산하는 데 필요하지 않은 다른 객체 저장
(3) ref 사용의 좋은 예시
다음의 원칙을 따르면 컴포넌트를 보다 쉽게 예측할 수 있다.
- refs를 *escape hatch로 간주한다.
: Refs는 외부 시스템이나 브라우저 API로 작업할 때 유용하다. 하지만, 애플리케이션 로직과 데이터 흐름의 상당 부분이 refs에 의존한다면 접근 방식을 재고해 보는 것이 좋다. - 렌더링 중에 ref.current를 읽거나 쓰지 말자.
: 렌더링 중에 일부 정보가 필요한 경우, state를 대신 사용하자. ref.current가 언제 변하는지 React는 모르기 때문에 렌더링할 때 읽어도 컴포넌트의 동작을 예측하기 어렵다.
💡 escape hatch
: 프레임워크의 일반적인 규칙이나 제한에서 벗어나, ‘특정한 목적을 달성할 수 있도록 하는 방법이나 기능’
리액트에서는 보통 데이터 흐름이 단방향이지만, 때로는 이 규칙을 우회해야 할 필요가 있을 때 escape hatch가 사용된다. 이런 경우, 상태 관리 라이브러리나 리액트 외부의 API를 통해 리액트의 일반적인 데이터 흐름을 우회할 수 있다.
'[Front-end] 개발자 공부' 카테고리의 다른 글
[개발 공부 79일차] React 공식 문서 | Escape Hatches (3) (1) | 2024.07.10 |
---|---|
[개발 공부 78일차] React 공식 문서 | Escape Hatches (2) (0) | 2024.07.09 |
[개발 공부 76일차] 스크랩 여부가 공유되는 문제 해결 (4) | 2024.07.02 |
[개발 공부 75일차] Managing State | React 공식 문서 Study (0) | 2024.07.01 |
[개발 공부 74일차] 댓글 미반영, 유저 유형 안 보이는 이슈 해결 (0) | 2024.06.24 |