본문 바로가기

Front-End: Web/React.js

useRef 란? (feat. RefObject)

반응형

useRef

useRef?

  • React Hook의 일종
  • 인자로 넘어온 초기값을 useRef 객체의 .current property에 저장한다.

 

사용 용도

  1. DOM 객체에 직접 접근하여 내부 값을 변경할 때
  2. focus() 메서드를 사용해야할 때
  3. 값이 변경되어도 컴포넌트에 리렌더링이 일어나지 않도록 할 때
    1. useRef 는 내용이 변경되어도 변경되었다는 사실을 알려주지 않는다.
    2. .current property를 변경시키면 리렌더링을 발생시키지 않음. 그래서 로컬 변수 용도로도 사용할 수 있다.
    3. ex. Input과 같은 태그에서 값을 입력할 때마다 컴포넌트에 리렌더링이 일어나지 않도록 하고 싶은 경우

그저 함수의 초기값을 .current에 저장한다고 보면 된다.

useRef의 반환 타입 종류인 MutableReObject 와 ReObject 의 정의를 살펴보면 이해할 수 있다!

interface MutableReObject<T> {
	current: T;
}

제너릭 타입을 사용하여 들어오는 타입을 useRef의 타입으로 지정해줌

interface ReObject<T> {
	readonly current: T | null;
}

위와 동일하게 들어오는 타입, 혹은 null을 타입으로 지정해줌

그러니까 둘 다 함수의 초기값을 .current에 저장하고 타입은 들어온 값의 타입으로 지정해준다.

 

useRef의 정의 3개

  • @types/react에 index.d.ts를 보면 useRef 훅은 정의 3개로 오버로딩되어 있다.
  • useRef의 정의는 3개이므로 경우에 따라서 다른 정의를 사용해야 한다. 근데 언제 어떤 정의가 쓰여야하는지 몰라서 항상 ref에 타입을 줄 때 많은 에러가 발생하곤 한다.
  • 그러니까 이 정의 3개를 알아야 한다!

 

1. useRef<T>(initialValue: T): MutableRefObject<T>

인자의 타입과 제너릭 타입이 T로 일치하는 경우, MutableReObject<T>를 반환한다.

MutableReObject<T>의 경우, 이름에서도 볼 수 있고 위에서 정의에서도 그렇듯이 current property 그 자체를 변경할 수 있다.

useRef<number>(0);

예제

import React, { useRef } from "react";

const App = () => {
  const localVarRef = **useRef<number>(0);**

  const handleButtonClick = () => {
		if (localVarRef.current) {
	    localVarRef.current += 1;
	    console.log(localVarRef.current);
		}
  };

  return (
    <div className="App">
      <button onClick={handleButtonClick}>+1</button>
    </div>
  );
};

export default App;

useRef를 로컬 변수 용도로 사용한 간단한 예제로, 버튼을 클릭할 때마다 localVarRef.current 가 +1한다.

위의 예제의 경우, useRef에 제네릭 타입과 동일한 타입인 초기 인자를 줬기 때문에 .current를 직접 수정할 수 있다.

 

2. useRef<T>(initialValue: T | null): RefObject<T>

인자의 타입이 null을 허용하는 경우, RefObject<T>를 반환한다.

하지만 위와 달리, RefObject<T>는 current 프로퍼티를 직접 수정할 수 없다.

useRef<number>(null);
useRef<HTMLInputElement>(null);

예제 - 1 : Primitive Type을 준 경우

위와 동일한 경우에서 초기 인자값을 null로 준 경우이다.

...
  const localVarRef = **useRef<number>(null);**

  const handleButtonClick = () => {
    localVarRef.current += 1;
    console.log(localVarRef.current);
  };
...

이 경우는 useRef에 준 제너릭 타입(number)과 와 초기 인자값의 타입(null)이 동일하지 않으므로 에러가 발생한다.

왜냐하면 RefObject<T> 타입인 경우에는 타입이 readonly이기 때문!

예제 - 2: Reference Type을 준 경우

import React, { useRef } from "react";

const App = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleButtonClick = () => {
    if (inputRef.current) {
      inputRef.current.value = "";
    }
  };

  return (
    <div className="App">
      <button onClick={handleButtonClick}>+1</button>

      <input ref={inputRef} />
      <button onClick={handleButtonClick}>Clear</button>
    </div>
  );
};

export default App;

해당 예제는 input DOM element를 ref로 받고, 버튼을 클릭할 때마다 input의 value를 빈 문자열로 직접 수정한다.

근데 위의 경우와 달리, 이 경우에는 에러 발생 없이 정상 동작한다.

왜냐하면 **readonly**는 shallow하기 때문이다! 정의 상 current 프로퍼티는 읽기 전용이므로, current 프로퍼티의 하위 프로퍼티인 value는 여전히 수정 가능하다. 그래서 current 프로퍼티를 직접 수정하려면 에러가 발생하겠지만, 그 아래인 value는 수정해도 에러 문제 없이 수정 가능한 것.

 

3. useRef<T = undefined>() : MutableRefObject<T | undefined>

제너릭 타입을 제공하지 않는 경우, <MutableRefObject<T | undefined>를 반환한다.

useRef<HTMLInputElement>();

예제

위의 input DOM element 예제에서 인자를 undefined 혹은 인자를 주지 말아보자.

...
  const inputRef = useRef<HTMLInputElement>();
...

그러면 input ref={inputRef} 에서 타입이 맞지 않는다는 에러가 발생한다.

왜냐하면 ref 프로퍼티는 RefObject 형만 받는데(정의 2인 제너릭엔 Primitive type, 인자엔 null을 준 경우만 가능),

inputRef는 인자에 아무 값도 넣어주지 않았으니 정의 상 MutableRefObject 가 되고,

이것을 ref 프로퍼티에 넣으려니 발생하는 에러인거다.

 

 

Recap

1. 로컬 변수 용도로 사용하는 경우

const localVarRef = useRef<number>(0);

로컬 변수 용도로 사용하는 경우, MutableRefObject<T> 를 사용해야 하므로 제네릭 타입과 동일한 타입의 초깃값을 넣어주자.

(단, 추론 가능한 간단한 타입들(ex. number, string, null, undefined, boolean)은 굳이 제네릭 타입을 주지 않아도 됨!)

 

2. DOM을 조작하기 위해 사용하는 경우

const inputRef = useRef<HTMLInputElement>(null);

DOM을 조작하기 위해 사용하는 경우, RefObject<T>를 사용해야 하므로 초기값으로 null을 넣어주자.

 

 

반응형