[#useRef, #TS2322, #MutableRefObject<undefined>]React-Typescript에서 useRef를 쓰면서 겪은 에러

calm·2022년 2월 13일
4

해당 글의 자료와 코드는 여기를 바탕으로 다시 정리해본 글입니다

다음과 같은 에러를 만났습니다

TS2322: Type 'MutableRefObject' is not assignable to type '((instance: HTMLInputElement | null) => void) | RefObject | null | undefined'.
Type 'MutableRefObject' is not assignable to type 'RefObject'.
Types of property 'current' are incompatible.
Type 'undefined' is not assignable to type 'HTMLInputElement | null'.

다음의 링크를 통해서 해결했습니다.

해당 링크는 여기를 클릭하면 확인가능합니다.

다음과 같은 코드를 작성하면서 발생했습니다.

const SignUp = () => {
  const emailRef = useRef();
  const passwordRef = useRef();
  const passwordConfirmRef = useRef();
  
  return (
    <>
     ...중략...
     <Form.Control type="email" ref={emailRef} required />
     ...중략...
     <Form.Control type="password" ref={passwordRef} required />
      ...중략...
      <Form.Control type="password" ref={passwordConfirmRef} required />
      ...중략...
    </>
  );
};

위 코드는 어떤 의미인가요? 무엇을 목적으로 작성한 코드인가요?

useRef를 이용해 변수(emailRef, passwordRef, passwordConfirmRef)를 정의하고,
사용하려는 엘리먼트에 ref를 지정했습니다.
  
각각의 변수(emailRef, passwordRef, passwordConfirmRef)에 input의 값을 저장하려고 했습니다.

어떻게 코드를 변경해 해결했나요?

const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const passwordConfirmRef = useRef<HTMLInputElement>(null);

변경한 코드는 어떤 의미를 갖나요?

const 변수명 = useRef<ref를 지정한 html 엘리먼트>(초깃값으로 설정할 (: 숫자,null,string..));

요약

  1. useRef로 로컬변수를 사용하고 싶으면,
const localValRef = useRef<number>(숫자)
  1. useRef로 HTML엘리먼트 객체를 참조하고 싶은 경우,
const emailRef = useRef<HTMLInputElement>(null)
  1. useRef는 useRef의 타입과 초깃값의 타입에 따라 다음의 2종류의 객체를 반환한다.
MutableRefObject : 변경이 가능한 객체
RefObject : 읽기전용, 즉 변경이 불가능한 객체

=> 변수, 즉 값으로서 관심있을 때는, Mutation 객체를 반환해야한다.
=> Mutation이 아닌, 참조 목적이라면 RefObject를 반환해야한다.

  1. useRef<타입>(인자)에

4-1 : 타입 과 인자가 "서로 같은 타입"인 경우,

const localValRef = useRef<number>(0)

=> useRef에 할당한 타입(T)과 동일한 타입의 객체를 얻을 수 있다. 여기서 number을 할당했으므로 localValRef는 숫자타입니다.

4-2 : 인자에 null을 허용하는 타입(T|null)을 할당할 경우,
RefObject를 반환한다. 위의 A경우와 달리 값은 readonly이므로 수정 불가능하다.

4-3 : 인자가 undefined 일 경우,
useRef는 MutableRefObject<T | undefined>를 반환한다.


목차

1. useRef의 타입정의

2. useRef의 반환타입

3. 상황별 useRef 사용하기

Case1. [#변경가능한 값,#로컬변수] useRef를 어떤 value값을 증가/감소 할 용도로 사용하고 싶을 때,

Case2.[#읽기전용의 값, #HTML엘리먼트객체에 사용할때] 값을 읽기전용으로 사용할 경우,


1. useRef의 타입정의

useRef는 다음의 3가지 타입정의를 갖습니다.

useRef<T>(initialValue: T): MutableRefObject<T>;
useRef<T>(initialValue: T|null): RefObject<T>;
useRef<T = undefined>(): MutableRefObject<T | undefined>;

2. useRef의 반환타입

useRef는 다음 2가지의 "반환타입"을 갖습니다

  1. MutationRefObject
interface MutableRefObject<T> {
    current: T;
}
  1. RefObject
interface RefObject<T> {
    readonly current: T | null;
}

useRef를 변수로 사용할 때, 초깃값은 .current에 저장됩니다.


즉, 타입스크립트에서 useRef를 어떻게 타입정의[위 3개]하느냐에 따라 반환되는 타입형태[위 2개]가 달라진다는 의미입니다.


Case1. [#변경가능한 값,#로컬변수] useRef를 어떤 value값을 증가/감소 할 용도로 사용하고 싶을 때,

Case2.[#읽기전용의 값, #HTML엘리먼트객체에 사용할때] 값을 읽기전용으로 사용할 경우,


[적용]

Case1. [#변경가능한 값, #로컬변수] useRef를 어떤 value값을 증가/감소 할 용도로 사용하고 싶을 때,

다음처럼 쓰면 된다.

const localVarRef = useRef<number>(0)

case1은 다음의 정의를 따른다.

useRef<T>(initialValue: T): MutableRefObject<T>;
interface MutableRefObject<T> {
    current: T;
}

[해석]

useRef우측, <>안에 들어가는 타입(T)과 inititialValue의 타입(T)가 서로 같으면,
MutabelRefObject가 반환되는데, 이때 같은 타입(T)으로 적용됩니다.
이때, useRef는 MutableRefobject 인터페이스를 따르는데, MutableRefobject의 타입으로 된 타입이 current의 타입(T)이 된다.

즉, useRef의 타입과 initialValue의 타입이 서로 일치하면, current도 동일하게 같은 타입(T)을 받게 됩니다.

const localVarRef = useRef<number>(0)일 경우,
useRef의 타입과 initialValue가 number타입이 되기 때문에 localVarRef는 number타입이 된다.

이때, MutationObjectRef가 반환하며, 이름처럼 Mutation, 즉 변경가능한 값이다.  

다음처럼 작성하면 됩니다.

const localVarRef = useRef<number>(0);

[해석]

const localVarRef = useRef<여기>(저기);

'여기'와 '저기'의 타입(예:number,string,boolean..)이 서로 같은 경우, current의 값을 변경하며 사용할 수 있다.

const localVarRef = useRef<number>(0);

0 과 number는 서로 타입이 일치함,

즉, useRef의 인자( )타입과 제네릭타입< >이 서로 일치 할 경우, MutableRefObject이 반환되어 값을 변경(Mutation)할 수 있다.

다음과 같은 경우,

const localVarRef = useRef<number>(0);

localVarRef는,

MutableRefObject<number> 타입이 된다.

그래서,
.current프로퍼티에 넣은 값을 수정해 사용할 수 있습니다.

[코드]

: 버튼을 클릭할 경우 localVarRef.current의 값이 1씩 증가하는 코드 입니다.

  
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;

Case2.[#읽기전용의 값, #HTML엘리먼트객체에 사용할때] 값을 읽기전용으로 사용할 경우,

다음처럼 쓰면 된다.

const emailRef = useRef<HTML엘리먼트객체>(null)

case2는 다음의 정의를 따른다.

useRef<T>(initialValue: T|null): RefObject<T>;
interface RefObject<T> {
    readonly current: T | null;
}

해석

inititialValue에 T|null 값을 허용할 경우,
useRef우측 <>에 입력한 타입T의 RefObject를 반환합니다.
반환되는 값은 RefObject로 수정 할 수 없는(readonly) current 프로퍼티를 갖습니다.

[코드]

: 버튼을 클릭하면 input의 value를 직접 빈 문자열로 수정하는 예제입니다.

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;

그러나!

inputRef.current.value = "";

초깃값(initialValue)에 null값이 들어올 경우, 수정 불가능한 RefObject가 반한됩니다.
이것은 current의 값을 수정 불가능한(readonly)한데 어떻게 (current아래) value에 값을 수정할 수 있을까요?

이는 readonly가 shallow(얕은객체복사)이기 때문입니다.

current는 변경이 불가능하나, current아래 value는 변경가능합니다.

profile
공부한 내용을 기록합니다

0개의 댓글