2022. 2. 4. 11:38ㆍProgramming/React
Input
의 onChange
에 setState
를 할당해서 값이 바뀔때마다 상태값이 변경되도록 설정했더니, 매번 Input
에 입력한 값이 변경될 때마다 렌더링이 다시 되는 기염을 토했다. 당연한 얘긴 줄 알고 있었는데 주리님이 보시고 ‘이럴때는 이런 식으로 최적화가 가능해요’라고 말씀해주셔서, react-hook-form
, react-final-form
, formic
같은 애들에 대해 알게됐다. 조금 고민해보다가 ‘엥? 얘는 일단 간단하니까 useRef
만 써도 되는거 아닐까?’라는 결론에 도달, Input
에 ref
를 냅다 꽂아버렸더니 LegacyRef
에 MutableRefObject
를 할당할 수 없다는 에러가 발생했다.
우선 Input
의 ref
를 보면 요 녀석은 LegacyRef|undefined
를 받게 되어있다. 요 녀석을 따라가보자.
Input
이 어째서 LegacyRef
를 참조하는지는 IntrinsicElement
를 살펴보면 알 수 있다. IntrinsicElement
는 Dom elements
에다, React
내에서 추가한 props
를 처리할 수 있도록 만들어놓은 녀석인 듯(?)하다. 요 녀석부터 살펴보자.
interface IntrinsicElements {
// HTML
a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
abbr: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
address: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
...
input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
...
type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E;
interface ClassAttributes<T> extends Attributes {
ref?: LegacyRef<T> | undefined;
}
interface RefObject<T> {
readonly current: T | null;
}
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
type Ref<T> = RefCallback<T> | RefObject<T> | null;
type LegacyRef<T> = string | Ref<T>;
IntrinsicElements
에서 사용하고 있는 DetailedHTMLProps
를 살펴보면, HTMLAttributes
와 ClassAttributes
의 Intersection Types이고, ClassAttributes
는 안에 LegacyRef
만 가지고 있는 녀석이다. 정리해보면 DetailedHTMLProps
는 HTMLAttributes
에 LegacyRef
만 추가된 녀석이라고 볼 수 있다. 와! 차근차근 살펴보니까 생각보다 심플해! 여기서 DetailedHTMLProps
의 E
는 React.InputHTMLAttributes<HTMLInputElement>
이고, T
는 HTMLInputElement
로 고정되는 것을 알 수 있다.
아무튼 LegacyRef
는 string|Ref
를 인자로 받을 수 있다. LegacyRef
가 string
을 받을 수 있는 이유에 대해 살펴보자면, React
가 Class components
를 사용하던 시절로 거슬러 올라가야 한다. 그 당시에는 ref
를 사용하기 위해선 키 값을 지정했기 때문에, 키 값에 해당하는 string
을 인자로 받는 코드가 남아있는 것이다. 자, 그럼 이제 LegacyRef
가 인자로 받을 수 있는 또 하나의 타입인 Ref
를 살펴보자.
Ref
는 RefCallback
과 RefObject
, 그리고 null
을 받을 수 있다. 위에서 T
는 HTMLInputElement
로 고정되었으니까, Ref
의 T
역시 HTMLInputElement
가 된다.
이제 useRef
를 살펴보자. useRef
는 넘겨준 T
에 따라 세 가지 방식으로 오버로딩되어있는데, useRef의 반환형 중 RefObject
를 찾아볼 수 있다. 반환형이 RefObject
가 아닌 MutableRefObject
인 경우가 있는데, 살펴보면 RefObject
와는 달리 current
를 변경 가능할 뿐, 똑같이 생긴 녀석임을 알 수 있다.
function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T|null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
interface RefObject<T> {
readonly current: T | null;
}
interface MutableRefObject<T> {
current: T;
}
여기서 다시 에러가 났던 코드로 돌아가자.
useRef
가 반환하는 녀석을 보면 MutableRefObject<HTMLInputElement | undefined>
를 반환하는 걸 볼 수 있다. 뭐, T
를 HTMLInputElement
로 주고 초기값이 undefined
니까 자연스럽게 T
가 HTMLInputElement|undefined
로 추론되는 것.
문제는 ref
의 T
가 IntrinsicElements
의 DetailedHTMLProps
에 의해서 HTMLInputElement
로 고정됐는데, useRef
의 반환형은 초기값이 undefined
이므로 T
가 HTMLInputElement | undefined
로 고정된다. 결국 여기서 T
가 다르기때문에, ref
에 내려꽂으면 에러가 발생하는 것!
이제 useRef<HTMLInputElement>()
를 useRef<HTMLInputElement>(null)
로 변경하면 무슨 일이 일어나는지 살펴보자.
useRef
의 반환형이 RefObject<HTMLInputElement>
로 바뀌었다! 위에서 살펴봤던 세 가지 형태의 useRef
에서, T
값이 변경되어 반환형도 달라졌기 때문이다.
function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T|null): RefObject<T>;
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
이제 할당하고자 하는 ref
의 타입과 useRef
의 반환형이 동일하므로, 에러가 발생하지 않게됐다. 와! 고민 해결!
'Programming > React' 카테고리의 다른 글
[React] Remix에 Pico.css를 적용해보자 (0) | 2021.12.21 |
---|