react-router tutorial을 typescript로 마이그레이션 하면서 loader에 대한 type을 어떻게 지정하고 그에 대한 데이터를 typescript가 어떻게 추론하게 할 수 있는지에 대한 장벽에 막혔었다.
문제를 여기서 fredericoo
라는 분의 솔루션을 통해 해결할 수 있었다.
useLoaderData를 통해 가져온 data에 대한 type을 추론시키기 위해서 loader의 type을 정의해주어야 한다.
fredericoo
분의 loader를 정의한 type이다.
import { LoaderFunction } from 'react-router-dom';
export type LoaderData<TLoaderFn extends LoaderFunction> = Awaited<ReturnType<TLoaderFn>> extends Response | infer D
? D
: never;
처음에는 이해 안 가는 부분이 있었지만, 차근히 뜯어보면 알 수 있다.
LoaderData<TLoaderFn extends LoaderFunction>
: 인자로 받는 함수는 LoaderFunction을 확장
해야 한다.Awaited<ReturnType<TLoaderFn>> extends Response
: 인자로 받는 함수의 return type이 Response 객체를 확장
한다면 Promise<Response>가 LoaderData type으로 확정
된다.infer D ? D : never
: 인자로 받는 함수의 return type이 Response를 확장하지 않을 때 type의 평가가 이뤄지는 구문으로 Promise가 아닐 때 타입을 추론한다.따라서, 배운 내용을 적용하면 아래와 같이 된다.
import { LoaderFunction, useLoaderData } from "react-router-dom";
import { createContact, getContacts } from "../contacts";
type LoaderData<TLoaderFunc extends LoaderFunction> = Awaited<
ReturnType<TLoaderFunc>
> extends Response | infer D
? D
: never;
type Contact = {
id: string;
createdAt: Date;
first: string;
last: string;
avatar: string;
twitter: string;
notes: string;
favorite: boolean;
};
export const loader = (async (): Promise<{ contacts: Contact[] }> => {
const contacts = await getContacts();
return { contacts };
}) satisfies LoaderFunction;
export default function Root() {
// const { contact } = useLoaderData() as { contact: Contact[] };
const { contacts } = useLoaderData() as LoaderData<typeof loader>;
return (...//)
}
이제 contact를 잘 추론하는지 확인해보자
contact의 type을 잘 추론하는 것을 볼 수 있다.
간편하게 const { contacts } = useLoaderData() as { contacts: Contact[] };
와 같이 해도 상관없을 것 같다. 하지만, 오류는 짐작할 수 없기에 나는 자세하게 type을 지정하는 것을 선호한다.
또한 이러한 과정 없이 빠르게 개발하고 싶은 사람은 이 문제의 솔루션을 제공한 사람이 만든 react-router-typesafe
를 설치해서 사용해도 좋을 것 같다.