본문 바로가기

Study

[React + TS] useRef 사용시 The expected type comes from property 'ref' which is declared here on type 에러 (+ useRef 3가지 정의)

프로젝트에서 useRef를 사용해서 input 컴포넌트에 focus를 주려고 했는데 다음과 같은 에러가 발생했다.

useRef에 타입을 지정해주지 않아 에러가 나는 듯 해서 useRef<HTMLInputElement>() 이렇게 타입을 지정해주었는데도 다음과 같은 에러가 발생했다.

에러를 해결하기 위해서 useRef의 타입이 어떻게 정의되어 있고 사용되는 목적에 따라 어떤 타입과 초깃값을 넣어주는게 좋을지 알아보았다.

 

먼저, useRef의 반환 타입은

interface MutableRefObject<T> {
    current: T;
}

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

 

위처럼 MutableRefObject<T> 타입인 currentRefObject<T> 타입인 current가 있다.

RefObject<T> 타입으로 반환될 경우에는 readonly 속성이 적용되어 current 값이 변경이 불가능하다.

또, @types/react의 index.d.ts를 보면 useRef 훅은 3개의 정의가 오버로딩 되어 있는 것을 볼 수 있다.

 

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

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

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

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

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

제네릭의 타입이 undefined인 경우(타입을 제공하지 않은 경우), MutableRefObject<T | undefined>를 반환한다.

MutableRefObject의 경우, 이름에서도 볼 수 있고 위의 정의에서도 확인할 수 있듯 current 프로퍼티 그 자체를 직접 변경할 수 있다.

3개의 정의를 예제와 함께 살펴보자.

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가 로컬 변수로 사용되는 예제이다.

useRef에 제네릭 타입과 동일한 타입의 초기 인자를 줬으므로 localVarRef는 MutableRefObject<number> 타입이 되어 current 값을 직접 변경 할 수 있다. 위 정의 중 1번의 경우에 해당된다.

만약 useRef 값을 null로 초기화 했다면

다음과 같은 에러가 나면서 current 프로퍼티를 수정 할 수 없다.

RefObject<T>로 반환되었기 때문에 위에서 보았듯 current 프로퍼티를 직접 수정할 수 없다. 위 정의 중 2번의 경우에 해당된다.

다른 예시코드도 살펴보자.

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를 빈 문자열로 수정하는 코드이다.
이때 useRef는 2번의 경우로 current.value 값 수정이 가능하다.

💡 2번의 경우는 RefObject<T>를 반환하는데 inputRef.current.value 수정이 가능한 이유?
current 프로퍼티만 readonly라고 정의되어 있고, readonly는 shallow 하기 때문에, current 프로퍼티의 하위 프로퍼티인 value는 수정이 가능하다. current 프로퍼티를 직접 수정하는 경우는 에러가 발생한다.


이때 useRef의 초깃값을 undefined로 주게되면 이 포스팅을 하게 된 이유가 되었던 에러가 발생한다.

 

이제 useRef의 모든 정의를 알아봤기 때문에 이 에러가 왜 나는지 알 수 있게 되었다.
inputRef는 정의 상 MutableRefObject가 되는데, ref 프로퍼티는 RefObject형만 받기 때문에 에러가 나는 것이다.

 

Ref의 사용 목적에 따라 정리하자면,

1. Ref를 지역 변수로 사용할 경우

const localVarRef = useRef<number>(0);

 

MutableRefObject<T>를 사용해야 하므로 제네릭 타입과 같은 초깃값을 넣어줘야 한다.

2. Ref를 DOM 조작을 위해 사용할 경우

const inputRef = useRef<HTMLInputElement>(null);

RefObject<T>를 사용해야 하므로 null로 초깃값을 넣어줘야 한다.

 

 

 

 

✨ 참고 문서 ✨

- https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5