우아한테크코스 레벨2 미션을 하며 ref를 필요로 하는 이벤트 핸들러가 커져 분리를 하고 싶었다.
가장 먼저 눈에 띈 부분은, 타입 가드 부분을 분리하고 싶었다.
const increaseQuantity = () => {
if (
typeof quantityRef === "function" ||
!quantityRef ||
!quantityRef.current
)
return;
const prevValue = +quantityRef.current.value;
quantityRef.current.value = (prevValue + 1).toString();
};
생각하기에 타입 가드를 그냥 분리하면 되겠다고 생각하겠지만, 타입 가드를 분리해 메서드로 선언해주면 특수한 작업이 필요하다.
먼저 ref 타입을 살펴보자.
type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
먼저, 미션에서는 상위의 ref를 사용하기 위해 forwardRef를 통해 전달받은 ref의 타입이다.
ForwardedRef는 크게 3가지 타입으로 평가 된다.
해당 이벤트 핸들러에서 타입을 안전하게 쓰려면 null과 ((instance: T | null) => void)가 되면 안된다. 따라서 다음과 같이 분리해야 한다.
export function isForwardedRef<T>(
ref: React.ForwardedRef<T>
): ref is MutableRefObject<T> {
return typeof ref !== "function" && ref !== null;
}
나는 MutableRefObject<HTMLInputElement>
를 원하므로 다음과 같이 분리했다.
typeof ref !== "function"
으로 ((instance: T | null) => void)
을 거르고
ref !== null
로 null
을 거를 수 있다.
따라서 이 타입 가드를 거치면 (ref is MutableRefObject<T>
) MutableRefObject<T | null>
타입을 보장할 수 있다.
이제 MutableRefObject<T | null>
로 타입을 한번 걸렀다. 그러나 아직 current는 null일 수 있다는 경고가 뜬다. 그 이유는 앞서 ref는 MutableObject<T | null>
까지만 평가되었기 때문이다.
type definition을 살펴보면 current 는 T 타입으로 정의 되므로 null
이 될 수도 있다.
우리는 HTMLInputElement가 ref의 current가 될 것을 알고 있지만, 안전하게 타입 가드를 하나 정의하자.
export function isRefCurrent<T>(current: T): current is T {
return current !== null;
}
단순히 null인지만 검사해주면 current의 Type을 보장해줄 수 있다.
그러면 타입 가드 부분을 간단히 만들어 줄 수 있다.
const increaseQuantity = () => {
if (!isForwardedRef<HTMLInputElement>(quantityRef)) return;
if (!isRefCurrent<HTMLInputElement>(quantityRef.current)) return;
// handle uncontrolled components
};