ReactNextCentral

Refs: 값 참조

Published on
Ref는 리액트 컴포넌트에서 렌더링과 무관한 정보를 저장하는데 사용되며, 이 문서에서는 ref의 활용과 주의사항에 대해 설명하고 있습니다.
Table of Contents

컴포넌트가 "기억"해야 할 정보가 있지만 해당 정보로 인해 새로운 렌더링이 발생하고 싶지 않을 때는 ref를 사용할 수 있습니다.

다음 내용을 배우게 됩니다.

  • 컴포넌트에 ref를 추가하는 방법
  • ref 값을 업데이트하는 방법
  • ref와 상태(state)의 차이점
  • 안전하게 ref를 사용하는 방법

컴포넌트에 ref 추가하기

리액트에서 useRef Hook을 가져와서 컴포넌트에 ref를 추가할 수 있습니다.

import { useRef } from 'react';

컴포넌트 내부에서 useRef Hook을 호출하고 참조할 초기 값을 유일한 인수로 전달합니다. 예를 들어, 값 0에 대한 ref를 생성하는 방법은 다음과 같습니다.

const ref = useRef(0);

useRef는 다음과 같은 객체를 반환합니다.

{ 
  current: 0 // useRef에 전달한 값
}

ref의 현재 값을 ref.current 속성을 통해 액세스할 수 있습니다. 이 값은 의도적으로 가변적입니다. 즉, 읽기와 쓰기 모두 가능합니다. 이는 리액트가 추적하지 않는 컴포넌트의 "비밀 포켓"과 같습니다. (이로 인해 리액트의 일방향 데이터 흐름에서 "탈출구"로 작용합니다. 아래에서 자세히 알아보겠습니다!)

여기서 버튼은 클릭할 때마다 ref.current 값을 증가시킵니다.

Edit upbeat-blackburn-lle79h

ref는 숫자를 가리키지만 상태(state)와 같이 문자열, 객체, 함수 등 모든 유형을 가리킬 수 있습니다. 그러나 ref는 state와 달리 읽기와 수정이 가능한 일반적인 JavaScript 객체입니다.

컴포넌트는 증가할 때마다 다시 렌더링되지 않습니다. state와 마찬가지로 ref는 리액트에 의해 재렌더링 사이에 유지됩니다. 그러나 state를 설정하면 컴포넌트가 재렌더링됩니다. 반면, ref를 변경하면 재렌더링되지 않습니다!

예제: 스톱워치 만들기

ref와 상태(state)를 단일 컴포넌트에서 함께 사용할 수 있습니다. 예를 들어, 사용자가 버튼을 눌러 시작하거나 중지할 수 있는 스톱워치를 만들어 보겠습니다. 사용자가 "시작" 버튼을 누른 후 경과한 시간을 표시하기 위해 "시작" 버튼이 눌린 시간과 현재 시간을 추적해야 합니다. 이 정보는 렌더링에 사용되므로 상태(state)에 유지할 것입니다.

const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);

사용자가 "시작" 버튼을 누르면 10밀리초마다 시간을 업데이트하기 위해 setInterval을 사용할 것입니다.

Edit xenodochial-galois-k3msuz

"중지" 버튼이 눌렸을 때는 기존의 간격을 취소하여 now 상태 변수를 업데이트하지 않도록 해야 합니다. 이를 위해 clearInterval을 호출해야 하지만 사용자가 "시작"을 누를 때 이전에 setInterval 호출에서 반환된 간격 ID를 지정해야 합니다. 간격 ID를 어딘가에 유지해야 합니다. 간격 ID는 렌더링에 사용되지 않으므로 ref에 유지할 수 있습니다.

Edit exciting-hertz-9trm60

정보가 렌더링에 사용되는 경우 상태(state)에 유지하고, 이벤트 처리기에서만 필요하며 변경되어도 재렌더링이 필요하지 않은 경우에는 ref를 사용하는 것이 더 효율적일 수 있습니다.

ref와 상태(state)의 차이점

아마도 ref가 상태(state)보다 "엄격하지 않다"는 생각이 드실지 모르겠습니다. 상태(state)를 변경하는 대신에 언제든지 값을 수정할 수 있기 때문입니다. 그러나 대부분의 경우 상태(state)를 사용하는 것이 좋습니다. ref는 종종 필요하지 않은 "탈출구"입니다. 상태(state)와 ref의 비교 방법은 다음과 같습니다.

refstate
useRef(initialValue){ current: initialValue }을 반환합니다.useState(initialValue)은 상태 변수의 현재 값과 상태 설정 함수([value, setValue])를 반환합니다.
변경시 재렌더링을 트리거하지 않습니다.변경시 재렌더링을 트리거합니다.
가변적입니다. 렌더링 프로세스 외부에서 current의 값을 수정하고 업데이트할 수 있습니다."불변적"입니다. 상태 변수를 수정하려면 상태 설정 함수를 사용하여 상태 변수를 수정하여 재렌더링을 예약해야 합니다.
렌더링 중에는 current 값을 읽거나 쓰지 않아야 합니다.언제든지 상태를 읽을 수 있습니다. 그러나 각 렌더링에는 자체적인 상태의 스냅샷이 있으며 변경되지 않습니다.

다음은 상태(state)를 사용하여 구현된 카운터 버튼 예제입니다.

Edit silly-easley-05cvxs

카운트 값이 표시되므로 상태(state) 값을 사용하는 것이 의미가 있습니다. count 값이 setCount()를 사용하여 설정될 때마다 리액트는 컴포넌트를 다시 렌더링하고 화면이 새로운 count를 반영합니다.

이를 ref로 구현하려고 하면 리액트가 컴포넌트를 다시 렌더링하지 않으므로 count가 변경되지 않아 텍스트가 업데이트되지 않습니다. 다음 버튼을 클릭해도 텍스트가 업데이트되지 않는 것을 확인하세요:

Edit serene-violet-t8jjlm

이는 렌더링 중 ref.current를 읽는 것이 신뢰할 수 없는 코드를 만든다는 이유입니다. 이 경우에는 상태(state)를 사용하세요.

자세히 알아보기: useRef는 내부에서 어떻게 작동할까요?

useStateuseRef는 모두 리액트에서 제공되지만, 원칙적으로 useState 위에 useRef를 구현할 수 있습니다. 리액트 내부에서 useRef가 다음과 같이 구현된 것처럼 생각할 수 있습니다.

// 리액트 내부에서
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}

첫 번째 렌더링에서 useRef{ current: initialValue }를 반환합니다. 이 객체는 리액트에 의해 저장되므로 다음 렌더링에서 동일한 객체가 반환됩니다. 여기서 상태 설정 함수는 이 예제에서 사용되지 않습니다. useRef는 항상 동일한 객체를 반환해야 하기 때문에 필요하지 않습니다!

리액트는 실제로 대부분의 경우에 useRef를 제공합니다. 그러나 ref를 사용하기 전용 상태 변수로 생각할 수 있습니다. 객체 지향 프로그래밍에 익숙하다면 ref가 인스턴스 필드를 생각나게 할 수 있습니다. 하지만 this.something 대신 somethingRef.current와 같이 작성합니다.

ref를 사용해야 할 때

일반적으로 컴포넌트가 리액트를 벗어나서 외부 API(일반적으로 컴포넌트의 외관에 영향을 주지 않는 브라우저 API)와 통신해야 할 때 ref를 사용합니다. 다음은 몇 가지 이러한 드물게 발생하는 상황입니다.

컴포넌트가 일부 값을 저장해야하지만 렌더링 로직에 영향을 주지 않는 경우 ref를 선택하세요.

ref를 사용하는 데 대한 모범 사례

다음 원칙을 따르면 컴포넌트가 예측 가능해집니다.

  • ref를 탈출구로 취급하세요. ref는 외부 시스템이나 브라우저 API와 작업할 때 유용합니다. 애플리케이션 로직과 데이터 흐름의 대부분이 ref에 의존한다면 접근 방식을 재검토하는 것이 좋습니다.
  • 렌더링 중에 ref.current를 읽거나 쓰지 마세요. 렌더링 중에 필요한 정보가 있는 경우 상태(state)를 사용하세요. 리액트는 ref.current가 언제 변경되는지를 모르기 때문에 렌더링 중에 읽는 것만으로도 컴포넌트의 동작을 예측하기 어렵게 만듭니다. (첫 번째 렌더링 중에만 ref.current = new Thing()과 같은 코드를 사용하는 경우는 예외입니다. 이 코드는 처음 렌더링 중에 ref를 한 번만 설정합니다.)

리액트 상태의 제한 사항은 ref에는 적용되지 않습니다. 예를 들어, 상태는 각 렌더링에 대한 스냅샷처럼 작동하고 동기적으로 업데이트되지 않습니다. 하지만 ref의 현재 값을 변경하면 즉시 변경됩니다.

ref.current = 5;
console.log(ref.current); // 5

이는 ref 자체가 일반적인 JavaScript 객체이기 때문에 JavaScript 객체처럼 작동하기 때문입니다.

또한 ref와 관련된 객체를 변경할 때 변경을 피해야 하는 걱정할 필요가 없습니다. 렌더링에 사용되지 않는 객체를 변경하는 경우 리액트는 ref나 그 내용물에 대해 신경쓰지 않습니다.

ref와 DOM

ref를 모든 값에 사용할 수 있지만, 가장 일반적인 사용 사례는 DOM 요소에 대한 접근입니다. 예를 들어, 입력란에 프로그래밍 방식으로 초점을 맞추려는 경우에 유용합니다. JSX의 ref 속성에 ref를 전달하면 리액트가 해당하는 DOM 요소를 myRef.current에 넣습니다. 이에 대해 자세히 알아보려면 DOM을 사용한 조작을 읽어보세요.

요약

  • ref는 렌더링에 사용되지 않는 값을 보유하기 위한 탈출구입니다. 자주 사용하지는 않습니다.
  • ref는 현재 값을 읽거나 설정할 수 있는 단일 속성인 current를 가진 일반적인 JavaScript 객체입니다.
  • useRef를 호출하여 리액트에게 ref를 제공하도록 요청할 수 있습니다.
  • 상태(state)와 마찬가지로 ref를 사용하면 컴포넌트 간에 재렌더링 사이에 정보를 유지할 수 있습니다.
  • 하지만 상태(state)와는 달리 refcurrent 값을 설정해도 재렌더링이 트리거되지 않습니다.
  • 렌더링 중에는 ref.curren를 읽거나 쓰지 마세요. 이로 인해 컴포넌트의 동작이 예측하기 어려워집니다.