Referencing Values with Refsref로 값 참조하기

When you want a component to “remember” some information, but you don’t want that information to trigger new renders, you can use a ref. 컴포넌트가 특정 정보를 ‘기억’하도록 하고 싶지만 해당 정보가 새 렌더링을 촉발하지 않도록 하려는 경우 ref를 사용할 수 있습니다.

You will learn학습 내용

  • How to add a ref to your component
  • How to update a ref’s value
  • How refs are different from state
  • How to use refs safely
  • 컴포넌트에 ref를 추가하는 방법
  • ref 값을 업데이트하는 방법
  • state와 ref의 차이점
  • ref를 안전하게 사용하는 방법

Adding a ref to your component컴포넌트에 ref 추가하기

You can add a ref to your component by importing the useRef Hook from React: React에서 useRef 훅을 가져와서 컴포넌트에 ref를 추가할 수 있습니다:

import { useRef } from 'react';

Inside your component, call the useRef Hook and pass the initial value that you want to reference as the only argument. For example, here is a ref to the value 0: 컴포넌트 내부에서 useRef 훅을 호출하고 참조할 초기값을 인자로 전달하십시오. 예를 들어, 값 0은 ref에 대한 초기값입니다:

const ref = useRef(0);

useRef returns an object like this: useRef는 다음과 같은 객체를 반환합니다:

{
current: 0 // The value you passed to useRef
}
An arrow with 'current' written on it stuffed into a pocket with 'ref' written on it.

Illustrated by Rachel Lee Nabors

You can access the current value of that ref through the ref.current property. This value is intentionally mutable, meaning you can both read and write to it. It’s like a secret pocket of your component that React doesn’t track. (This is what makes it an “escape hatch” from React’s one-way data flow—more on that below!) ref.current 속성을 통해 해당 ref의 현재 값에 액세스할 수 있습니다. 이 값은 의도적으로 변이 가능하므로 읽기와 쓰기가 모두 가능합니다. React가 추적하지 않는 컴포넌트의 비밀 주머니와 같습니다. (이것이 바로 React의 단방향 데이터 흐름에서 “탈출구”가 되는 이유입니다. 아래에서 자세히 설명합니다!)

Here, a button will increment ref.current on every click: 여기서 버튼은 클릭할 때마다 ref.current를 증가시킵니다:

import { useRef } from 'react';

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>
  );
}

The ref points to a number, but, like state, you could point to anything: a string, an object, or even a function. Unlike state, ref is a plain JavaScript object with the current property that you can read and modify. 여기서의 ref는 숫자를 가리키고 있지만, state와 마찬가지로 문자열, 객체, 함수 등 무엇이든 가리킬 수 있습니다. state와 달리 ref는 current 속성을 읽고 수정할 수 있는 일반 JavaScript 객체입니다.

Note that the component doesn’t re-render with every increment. Like state, refs are retained by React between re-renders. However, setting state re-renders a component. Changing a ref does not! 컴포넌트는 ref가 증가할 때마다 리렌더링되지 않는다는 점에 유의하세요. state와 마찬가지로 ref는 리렌더링 사이에 React에 의해 유지됩니다. state를 설정하면 컴포넌트가 다시 렌더링됩니다. 반면 ref를 변경하면 그렇지 않습니다!

Example: building a stopwatch예제: 스톱워치 만들기

You can combine refs and state in a single component. For example, let’s make a stopwatch that the user can start or stop by pressing a button. In order to display how much time has passed since the user pressed “Start”, you will need to keep track of when the Start button was pressed and what the current time is. This information is used for rendering, so you’ll keep it in state: ref와 state를 단일 컴포넌트로 결합할 수 있습니다. 예를 들어, 사용자가 버튼을 눌러 시작하거나 중지할 수 있는 스톱워치를 만들어 봅시다. 사용자가 ‘Start’를 누른 후 얼마나 시간이 지났는지 표시하려면 시작 버튼을 누른 시점과 현재 시간을 추적해야 합니다. 이 정보는 렌더링에 사용되므로 state를 유지해야 합니다:

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

When the user presses “Start”, you’ll use setInterval in order to update the time every 10 milliseconds: 사용자가 ‘Start’를 누르면 10밀리초마다 시간을 업데이트하기 위해 setInterval을 사용하게 됩니다:

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  function handleStart() {
    // Start counting.
    setStartTime(Date.now());
    setNow(Date.now());

    setInterval(() => {
      // Update the current time every 10ms.
      setNow(Date.now());
    }, 10);
  }

  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>
    </>
  );
}

When the “Stop” button is pressed, you need to cancel the existing interval so that it stops updating the now state variable. You can do this by calling clearInterval, but you need to give it the interval ID that was previously returned by the setInterval call when the user pressed Start. You need to keep the interval ID somewhere. Since the interval ID is not used for rendering, you can keep it in a ref: “Stop” 버튼을 누르면 now state 변수의 업데이트를 중지하도록 기존 interval을 취소해야 합니다. 이 작업은 clearInterval을 호출하여 수행할 수 있지만, 사용자가 시작을 눌렀을 때 이전에 setInterval 호출에서 반환한 interval ID를 제공해야 합니다. interval ID를 어딘가에 보관해야 합니다. interval ID는 렌더링에 사용되지 않으므로 ref에 보관할 수 있습니다.

import { useState, useRef } from 'react';

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>
    </>
  );
}

When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn’t require a re-render, using a ref may be more efficient. 렌더링에 정보가 사용되는 경우 해당 정보를 state로 유지하세요. 이벤트 핸들러만 정보를 필요로 하고 변경해도 다시 렌더링할 필요가 없는 경우, ref를 사용하는 것이 더 효율적일 수 있습니다.

Differences between refs and stateref와 state의 차이점

Perhaps you’re thinking refs seem less “strict” than state—you can mutate them instead of always having to use a state setting function, for instance. But in most cases, you’ll want to use state. Refs are an “escape hatch” you won’t need often. Here’s how state and refs compare: 어쩌면 ref가 state보다 덜 “엄격”해 보인다고 생각할 수도 있습니다. 항상 state 설정자 함수를 사용하지 않고도 변이할 수 있기 때문입니다. 하지만 대부분의 경우 state를 사용하고 싶을 것입니다. ref는 자주 사용하지 않는 “탈출구”입니다. state와 ref를 비교하면 다음과 같습니다:

refsstate
useRef(initialValue) returns { current: initialValue }useState(initialValue) returns the current value of a state variable and a state setter function ( [value, setValue])
Doesn’t trigger re-render when you change it.Triggers re-render when you change it.
Mutable—you can modify and update current’s value outside of the rendering process.”Immutable”—you must use the state setting function to modify state variables to queue a re-render.
You shouldn’t read (or write) the current value during rendering.You can read state at any time. However, each render has its own snapshot of state which does not change.
refsstate
useRef(initialValue){ current: initialValue }을 반환useState(initialValue)는 state 변수의 현재값과 state 설정자함수([value, setValue])를 반환
변경 시 리렌더링을 촉발하지 않음변경 시 리렌더링을 촉발함
Mutable— 렌더링 프로세스 외부에서 current 값을 수정하고 업데이트할 수 있음”Immutable”— state setting 함수를 사용하여 state 변수를 수정해 리렌더링을 대기열에 추가해야함
렌더링 중에는 current 값을 읽거나 쓰지 않아야 함언제든지 state를 읽을 수 있음. 각 렌더링에는 변경되지 않는 자체 state snapshot이 있음

Here is a counter button that’s implemented with state: 다음은 state와 함께 구현된 카운터 버튼입니다:

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      You clicked {count} times
    </button>
  );
}

Because the count value is displayed, it makes sense to use a state value for it. When the counter’s value is set with setCount(), React re-renders the component and the screen updates to reflect the new count. count 값이 표시되므로 state 값을 사용하는 것이 합리적입니다. 카운터 값이 setCount()로 설정되면 React는 컴포넌트를 다시 렌더링하고 화면이 새로운 카운트를 반영하도록 업데이트합니다.

If you tried to implement this with a ref, React would never re-render the component, so you’d never see the count change! See how clicking this button does not update its text: 만약 이것을 ref로 구현하려고 한다면, React는 컴포넌트를 다시 렌더링하지 않으므로 카운트가 변경되는 것을 볼 수 없을 것입니다! 이 버튼을 클릭해도 텍스트가 업데이트되지 않는 방법을 확인하세요:

import { useRef } from 'react';

export default function Counter() {
  let countRef = useRef(0);

  function handleClick() {
    // This doesn't re-render the component!
    countRef.current = countRef.current + 1;
  }

  return (
    <button onClick={handleClick}>
      You clicked {countRef.current} times
    </button>
  );
}

This is why reading ref.current during render leads to unreliable code. If you need that, use state instead. 이것이 렌더링 중에 ref.current를 읽으면 코드가 불안정해지는 이유입니다. 필요하다면 state를 대신 사용하세요.

Deep Dive | 심층 탐구

How does useRef work inside?ref는 내부에서 어떻게 작동하나요?

Although both useState and useRef are provided by React, in principle useRef could be implemented on top of useState. You can imagine that inside of React, useRef is implemented like this: useStateuseRef는 모두 React에서 제공하지만, 원칙적으로 useRefuseState 위에 구현될 수 있습니다. React 내부에서 useRef는 다음과 같이 구현된다고 상상할 수 있습니다:

// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}

During the first render, useRef returns { current: initialValue }. This object is stored by React, so during the next render the same object will be returned. Note how the state setter is unused in this example. It is unnecessary because useRef always needs to return the same object! 첫 번째 렌더링 중에 useRef{ current: initialValue }를 반환합니다. 이 객체는 React에 의해 저장되므로 다음 렌더링 중에 동일한 객체가 반환됩니다. 이 예제에서 state setter가 어떻게 사용되지 않는지 주목하세요. useRef는 항상 동일한 객체를 반환해야 하기 때문에 불필요합니다!

React provides a built-in version of useRef because it is common enough in practice. But you can think of it as a regular state variable without a setter. If you’re familiar with object-oriented programming, refs might remind you of instance fields—but instead of this.something you write somethingRef.current. React는 충분히 일반적인 상황이라 판단하고 빌트인 버전의 useRef를 제공합니다. ref를 설정자가 없는 일반 state 변수라고 생각하면 됩니다. 객체지향 프로그래밍에 익숙하다면 인스턴스 필드를 떠올릴 수 있는데, this.something 대신 somethingRef.current를 사용하면 됩니다.

When to use refsref를 사용해야 하는 경우

Typically, you will use a ref when your component needs to “step outside” React and communicate with external APIs—often a browser API that won’t impact the appearance of the component. Here are a few of these rare situations: 일반적으로 ref는 컴포넌트가 React로부터 “외부로 나가서” 외부 API, 즉,컴포넌트의 형상에 영향을 주지 않는 브라우저 API 등과 통신해야 할 때 사용합니다. 다음은 이러한 드문 상황 중 몇가지입니다:

If your component needs to store some value, but it doesn’t impact the rendering logic, choose refs. 컴포넌트에 일부 값을 저장해야 하지만 렌더링 로직에는 영향을 미치지 않는 경우 ref를 선택하세요.

Best practices for refsref 모범 사례

Following these principles will make your components more predictable: 다음 원칙을 따르면 컴포넌트의 예측 가능성을 높일 수 있습니다:

  • Treat refs as an escape hatch. Refs are useful when you work with external systems or browser APIs. If much of your application logic and data flow relies on refs, you might want to rethink your approach.
  • Don’t read or write ref.current during rendering. If some information is needed during rendering, use state instead. Since React doesn’t know when ref.current changes, even reading it while rendering makes your component’s behavior difficult to predict. (The only exception to this is code like if (!ref.current) ref.current = new Thing() which only sets the ref once during the first render.)
  • ref를 탈출구로 취급하세요. ref는 외부 시스템이나 브라우저 API로 작업할 때 유용합니다. 애플리케이션 로직과 데이터 흐름의 대부분이 ref에 의존하는 경우 접근 방식을 재고해봐야 할 수도 있습니다.
  • 렌더링 중에는 ref.current를 읽거나 쓰지 마세요. 렌더링 중에 일부 정보가 필요한 경우, 대신 state를 사용하세요. React는 ref.current가 언제 변경되는지 알지 못하기 때문에, 렌더링 중에 읽어도 컴포넌트의 동작을 예측하기 어렵습니다. (유일한 예외는 첫 번째 렌더링 중에 ref를 한 번만 설정하는 if (!ref.current) ref.current = new Thing()과 같은 코드입니다).

Limitations of React state don’t apply to refs. For example, state acts like a snapshot for every render and doesn’t update synchronously. But when you mutate the current value of a ref, it changes immediately: React state의 제한은 ref에는 적용되지 않습니다. 예를 들어, state는 모든 렌더링에 대해 스냅샷처럼 작동하며 동기적으로 업데이트되지 않습니다. 하지만 ref의 현재 값을 변이하면 즉시 변경됩니다:

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

This is because the ref itself is a regular JavaScript object, and so it behaves like one. 이는 ref 자체가 일반 JavaScript 객체이므로 JavaScript 객체처럼 동작하기 때문입니다.

You also don’t need to worry about avoiding mutation when you work with a ref. As long as the object you’re mutating isn’t used for rendering, React doesn’t care what you do with the ref or its contents. 또한 ref로 작업할 때 변이를 피하고자 조심할 필요가 없습니다. 변이하는 객체가 렌더링에 사용되지 않는 한, React는 ref나 그 콘텐츠로 무엇을 하든 상관하지 않습니다.

Refs and the DOMRef와 DOM

You can point a ref to any value. However, the most common use case for a ref is to access a DOM element. For example, this is handy if you want to focus an input programmatically. When you pass a ref to a ref attribute in JSX, like <div ref={myRef}>, React will put the corresponding DOM element into myRef.current. Once the element is removed from the DOM, React will update myRef.current to be null. You can read more about this in Manipulating the DOM with Refs. ref는 모든 값을 가리킬 수 있습니다. 그러나 ref의 가장 일반적인 사용 사례는 DOM 요소에 액세스하는 것입니다. 예를 들어, 프로그래밍 방식으로 input에 focus를 맞추고자 할 때 유용합니다. <div ref={myRef}>와 같이 JSX의 ref 어트리뷰트에 ref를 전달하면 React는 해당 DOM 엘리먼트를 myRef.current에 넣습니다. 엘리먼트가 DOM에서 제거되면 React는 myRef.currentnull로 업데이트합니다. 이에 대한 자세한 내용은 ref로 DOM 조작하기에서 확인할 수 있습니다.

Recap요약

  • Refs are an escape hatch to hold onto values that aren’t used for rendering. You won’t need them often.
  • A ref is a plain JavaScript object with a single property called current, which you can read or set.
  • You can ask React to give you a ref by calling the useRef Hook.
  • Like state, refs let you retain information between re-renders of a component.
  • Unlike state, setting the ref’s current value does not trigger a re-render.
  • Don’t read or write ref.current during rendering. This makes your component hard to predict.
  • ref는 렌더링에 사용되지 않는 값을 유지하기 위한 탈출구입니다. 자주 필요하지 않습니다.
  • ref는 current라는 단일 프로퍼티를 가진 일반 JavaScript 객체로, 읽거나 설정할 수 있습니다.
  • useRef 훅을 호출하여 React에 ref를 제공하도록 요청할 수 있습니다.
  • state와 마찬가지로 ref를 사용하면 컴포넌트의 리렌더링 사이에 정보를 유지할 수 있습니다.
  • state와 달리 ref의 current 값을 설정해도 리렌더링이 촉발되지 않습니다.
  • 렌더링 중에는 ref.current를 읽거나 쓰지 마세요. 이렇게 하면 컴포넌트를 예측하기 어렵습니다.

Challenge 1 of 4: Fix a broken chat input잘못된 chat input 고치기

Type a message and click “Send”. You will notice there is a three second delay before you see the “Sent!” alert. During this delay, you can see an “Undo” button. Click it. This “Undo” button is supposed to stop the “Sent!” message from appearing. It does this by calling clearTimeout for the timeout ID saved during handleSend. However, even after “Undo” is clicked, the “Sent!” message still appears. Find why it doesn’t work, and fix it. 메시지를 입력하고 “Send”를 클릭하세요. “Sent!”라는 알림이 표시되기까지 3초 정도 지연되는 것을 확인할 수 있습니다. 이 지연 시간 동안 “Undo” 버튼이 표시됩니다. 이 버튼을 클릭하세요. 이 “Undo” 버튼은 “Sent!” 메시지가 표시되지 않도록 하기 위한 것입니다. 이 버튼은 handleSend 중에 저장된 timeout ID에 대해 clearTimeout을 호출하여 이를 수행합니다. 그러나 “Undo”를 클릭한 후에도 “Sent!” 메시지가 계속 표시됩니다. 작동하지 않는 이유를 찾아서 수정하세요.

import { useState } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  let timeoutID = null;

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('Sent!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}