본문 바로가기

개발/React

[React] useRef

이번 학습 단계의 핵심 두 가지

  • 화면을 다시 그릴 필요가 없는 값을 기억할 때 useRef를 쓸 수 있다.
  • 특정 DOM 요소에 직접 접근할 때도 useRef를 쓴다.

Step 0. useRef

먼저 State와 비교해보자.

const [count, setCount] = useState(0);

 

State는 값이 바뀌면

State 변경
↓
컴포넌트 다시 렌더링
↓
화면 업데이트

가 된다.

 

반면 useRef는 값이 바뀌어도 렌더링을 발생시키지 않는다.

const countRef = useRef(0);

 

그리고 값은

countRef.current

에 들어 있다.

 

Step 1. 단순한 useRef

import { useRef } from "react";

function App() {
  const numberRef = useRef(0);

  function handleClick() {
    numberRef.current = numberRef.current + 1;

    console.log(numberRef.current);
  }

  return <button onClick={handleClick}>클릭</button>;
}

export default App;

 

버튼을 누르면

 

Console에서는

 

1 → 2 → 3 → 4

처럼 값이 변한다.

 

하지만 화면은 바뀌지 않는다.

 

왜냐하면

numberRef.current = numberRef.current + 1;

은 State 변경이 아니기 때문이다.

 

State와 Ref의 차이를 보면

State
→ 값을 기억
→ 변경되면 다시 렌더링

Ref
→ 값을 기억
→ 변경되어도 다시 렌더링하지 않음

즉, 값이 바뀌었을 때 화면도 바뀌어야 한다면 보통 State이고 반대로 화면과 직접 관계없는 내부 값이라면 Ref가 선택지가 될 수 있다.

 

Step 2. useRef의 주 사용처

바로 DOM 요소에 접근하기이다.

 

예를 들어

<input />

에 자동으로 커서를 넣고 싶다고 해보자.

 

JavaScript만을 사용하는 경우에는 DOM 요소에 직접 접근하기 위해

const input = document.querySelector("input");

input.focus();

처럼 한다.

 

React에서는 해당 input을 어떻게 가져올까?

 

useRef를 사용한다.

 

Step 3. input에 포커스 주기

다음 코드를 작성해보자.

 

src/App.jsx

import { useRef } from "react";

function App() {
  const inputRef = useRef(null);

  function handleFocus() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} />

      <button onClick={handleFocus}>
        입력창 포커스
      </button>
    </div>
  );
}

export default App;

버튼을 누르면 입력창에 커서가 들어간다.

 

살펴보면

 

먼저 import 한다.

import { useRef } from "react";

 

그리고 Ref 객체를 하나 생성한다.

const inputRef = useRef(null);

 

그다음 원하는 DOM 요소와 연결한다.

<input ref={inputRef} />

 

그러면 React가 이 실제 input DOM 요소를

inputRef.current

에 넣어준다.

 

즉, 

inputRef.current → 실제 input DOM 요소

이다.

 

Step 4. current는 왜 붙을까?

useRef()를 호출하면 대략 이런 형태의 객체를 받는다.

{
  current: null
}

 

처음에는 아직 DOM 요소가 연결되지 않았으므로

inputRef.current === null

이다.

 

React가 <input>을 실제 DOM에 연결하고 나면

inputRef.current

가 실제 input 요소를 가리키게 된다.

 

처음

inputRef
└─ current: null


렌더링 후

inputRef
└─ current: 실제 input DOM

 

 

Step 5. useState와 useRef를 직접 비교해보자.

다음 코드를 실행해보자.

import { useRef, useState } from "react";

function App() {
  const [stateCount, setStateCount] = useState(0);
  const refCount = useRef(0);

  function increaseState() {
    setStateCount(stateCount + 1);
  }

  function increaseRef() {
    refCount.current = refCount.current + 1;

    console.log(refCount.current);
  }

  return (
    <div>
      <p>State: {stateCount}</p>
      <p>Ref: {refCount.current}</p>

      <button onClick={increaseState}>
        State +1
      </button>

      <button onClick={increaseRef}>
        Ref +1
      </button>
    </div>
  );
}

export default App;

State +1을 누르면 화면이 바로 바뀐다.
그러나 Ref +1은 눌러도 Console 숫자는 증가하지만 화면의 Ref: 값은 바로 갱신되지 않는다.

왜냐하면

refCount.current = refCount.current + 1;


은 다시 렌더링을 발생시키지 않기 때문이다. Ref: 값은 다른 요인에 의해 다시 렌더링이 발생하는 경우에 갱신된다.

 

따라서 화면에 보여줄 값은 Ref로 관리하면 안 된다.

 

반면 화면을 다시 그릴 필요 없이 기억만 해야 하는 값이라면 Ref가 후보가 될 수 있다.

 

예를 들어,

DOM 요소
타이머 ID
이전 값 저장
렌더링과 무관한 mutable 값

같은 경우이다.

 

Step 6. useState와 useRef를 사용하는 판단 기준

값이 바뀌면 화면도 바뀌어야 한다
→ useState

값을 기억해야 하지만
바뀌어도 화면을 다시 그릴 필요가 없다
→ useRef

 

그리고 DOM에 직접 접근해야 한다면

DOM 접근
→ useRef

 

실습

다음 동작을 하는 웹 페이지를 만들어보자.

[React 검색              ]

[검색어 지우기]
[입력창 포커스]

검색어 지우기 버튼은 State를 사용하고,
입력창 포커스 버튼은 Ref를 사용한다.

 

기본 골격은 다음과 같다.

import { useRef, useState } from "react";

function App() {
  const [keyword, setKeyword] = useState("");
  const inputRef = useRef(null);

  function handleClear() {
    // 작성
  }

  function handleFocus() {
    // 작성
  }

  return (
    <div>
      <input
        ref={inputRef}
        value={keyword}
        onChange={(event) => setKeyword(event.target.value)}
      />

      <button onClick={handleClear}>검색어 지우기</button>

      <button onClick={handleFocus}>입력창 포커스</button>
    </div>
  );
}

export default App;

 

 

답안

function handleClear() {
  setKeyword("");
}

function handleFocus() {
  inputRef.current.focus();
}