[WIL] Week 5주차

Chanyoung Park·2024년 7월 22일
0

2024 이노베이션캠프

목록 보기
13/16

5주차 - Level5완성 & 팀 프로젝트 시작

Fact

Level 5 완성

https://level5-cs-app.vercel.app/

팀 프로젝트 기획

Feeling

1. 제네릭 & 타입 제한

  • 상황
    • addNewCsCard제네릭 함수T타입을 매개변수로 받을 수 있음

    • 매개변수를 그저 T 라고 선언할 경우, 함수 사용시 어떠한 타입도 들어갈 수 있는 문제 발생

    • 이는 정적 타이핑의 장점을 보지 못함

      export const addNewCsCard = async <T>(data:T) {}
      
      // 사용
      addNewCsCard<들어가면_안되는_타입>
    • 이를 방지하기 위해, T extends 사용

  1. 제네릭에서의 extends제한의 의미
  2. 제네릭 T는 무엇이든지 받을 수 있지만, 그 범위를 제한하려면 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);
};

2. zustand - store&공통기능 관리

  • zustandglobal state와 공통기능을 정의하여, 여러 컴포넌트에서 재사용&일관된 상태관리를 할 수 있었음

3. react-hook-form & yup

  • react-hook-form을 사용할 때, 검증기능을 사용하기 위해서는 input에 직접 validation을 붙여줘야 했음
<S.input.Input type="text" {...register("title", {required:true)} />
{errors.title && <S.span.ErrorSpan>제목을 입력해주세요.</S.span.ErrorSpan>
  • 이럴 경우, validation의 재사용이 필요할 경우, 모든 input에 찾아가 수정해주어야 하는 문제 생김

yup resolver활용

// 사용할 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>

4. Suspense & useSuspenseQuery

  • 보통 비동기 통신에 의한 렌더링은 IF 문을 통한 명령형으로 작성하곤 한다.
  • 이를 선언형으로 변경하기 위해, React.Suspense와 useSuspenseQuery활용
    • 예전에는, useQuery의 옵션으로 {suspense:true}를 줘야 했지만, v5에서는 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>
  );
};

Feeling & Future

  • 제네릭을 활용해서 구조적 타이핑의 장점을 얻어갔던 것이 TS를 제대로 사용하는 느낌이 들었다.
  • Yup Resolver를 활용해서 form 검증로직을 하나의 파일에서 관리했던 경험이 좋았다.
profile
더 나은 개발경험을 생각하는, 프론트엔드 개발자입니다.

0개의 댓글