https://level5-cs-app.vercel.app/
addNewCsCard는 제네릭 함수로 T타입
을 매개변수로 받을 수 있음
매개변수를 그저 T
라고 선언할 경우, 함수 사용시 어떠한 타입
도 들어갈 수 있는 문제 발생
이는 정적 타이핑의 장점을 보지 못함
export const addNewCsCard = async <T>(data:T) {}
// 사용
addNewCsCard<들어가면_안되는_타입>
이를 방지하기 위해, T extends
사용
extends
는 제한의 의미참고 : https://inpa.tistory.com/entry/TS-📘-타입스크립트-Generic-타입-정복하기
// 함수 선언
export type NewAuthCardReq = Omit<ICsCard, "id"> & {
userId: string;
};
export type NewCardReq = Omit<ICsCard, "id"> & {
password: string;
};
export const addNewCsCard = async <T extends NewAuthCardReq | NewCardReq>(
data: T
): Promise<ICsCard> => {
if (data.password) data.password = encrypt(data.password);
const res = await api.post<ICsCard>(BASE_URL, data);
return res.data;
};
// 함수 사용
// 인증사용자의 Card추가
const onAddNewCardAuth = async (data: AuthCardFormSchema): Promise<void> => {
if (!userId) return;
const res = await addNewCsCard<NewAuthCardReq>({ ...data, userId });
onSuccess(res.id);
};
// 익명사용자의 Card추가
const onAddNewCard = async (data: CardFormSchema): Promise<void> => {
const res = await addNewCsCard<NewCardReq>(data);
onSuccess(res.id);
};
validation
을 붙여줘야 했음<S.input.Input type="text" {...register("title", {required:true)} />
{errors.title && <S.span.ErrorSpan>제목을 입력해주세요.</S.span.ErrorSpan>
// 사용할 schema 정의
import * as yup from "yup";
const title = yup.string().required("제목을 입력해주세요.");
const content = yup.string().required("내용을 입력해주세요.");
export const authCardFormSchema = yup.object().shape({
title,
content,
});
export type AuthCardFormSchema = yup.InferType<typeof authCardFormSchema>;
// form
const {
register,
handleSubmit,
formState: { errors },
} = useForm<AuthCardFormSchema>({
defaultValues: csCard,
resolver: yupResolver(authCardFormSchema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="title">제목</label>
<S.input.Input type="text" {...register("title")} />
{errors.title && <S.span.ErrorSpan>{errors.title.message}</S.span.ErrorSpan>}
<S.div.Gap $height={20} $width={0} />
<label htmlFor="content">내용</label>
<S.input.TextArea {...register("content")} />
{errors.content && <S.span.ErrorSpan>{errors.content.message}</S.span.ErrorSpan>}
<S.div.Gap $height={20} $width={0} />
<S.button.Button $fullWidth type="submit">
{isEditMode.current ? "작성" : "수정"}
</S.button.Button>
</form>
명령형
으로 작성하곤 한다.선언형
으로 변경하기 위해, React.Suspense와 useSuspenseQuery활용
// CardDetailPage.tsx
<Suspense fallback={<LoadingFallbackUI />}>
<h2>CS카드</h2>
<CsCardPaper id={Number(id)} />
</Suspense>
//CsCardPaper.tsx
const CsCardPaper = ({ id }: { id: number }) => {
const { data: card } = useSuspenseQuery({
queryKey: ["csCards", id],
queryFn: ({ queryKey }) => getCsCardById(queryKey[1] as number),
});
return (
<S.div.Paper>
<CsCard.Header />
{isEditMode ? (
<CsCard.EditForm />
) : (
<Fragment>
<h1>{card.title}</h1>
<hr />
<h3>{card.content}</h3>
</Fragment>
)}
</S.div.Paper>
);
};
구조적 타이핑
의 장점을 얻어갔던 것이 TS를 제대로 사용하는 느낌이 들었다.Yup Resolver
를 활용해서 form 검증로직을 하나의 파일에서 관리했던 경험이 좋았다.