[React, TypeScript] types

Park Bumsoo·2022년 6월 8일
0

프로젝트 진행중 무수한 any를 보고 "이러면 JS와 다를게 없지않나?"라는 생각 이 들어 모든 any를 지우는 목적으로 TypeScript를 열심히 작성해봤다.

그 과정중 공부한 내용들이다.

1. Generic

generic 타입은 어떤 타입인지 모르지만 들어온 타입을 그대로 사용한다.
문자/숫자/bool가 들어오면 선언된 함수 전체가 문자/숫자/bool 사용
즉 들어온 타입을 그대로 사용하는 타입이며, 모든 타입을 허용하는any와 다르게 return할 값의 타입이 예측이 가능하다.

function getGeneric<MyType>(arg: MyType): MyType {
  return arg;
}

const aaa: string = "철수";
const bbb: number = 8;
const ccc: boolean = true;

const reuslt4_1 = getGeneric(aaa);
const reuslt4_2 = getGeneric(bbb);
const reuslt4_3 = getGeneric(ccc);

위 코드의 경우 argtypeMyType이며, return arg;에 대한 타입은 (arg: MyType): MyType 맨 앞의 <MyType>는 적용된 모든 <MyType>에 들어온 타입(string, number, boolean....)으로 명시해준다는 의미를 가진다.

function getGenerics<MyType1, MyType2, MyType3>(
  arg1: MyType1, 
  arg2: MyType2,
  arg3: MyType3
): [MyType3, MyType2, MyType1] {
  return [arg3, arg2, arg1];
}

const result6 = getGenerics("철수", "다람쥐초등학교", 8);

배열로 return을 가져올 경우 위처럼 사용이 가능하다.

추가로

// 8.gereric 타입2 - 축약1
function getGenerics<T, U, V>(arg1: T, arg2: U, arg3: V): [V, U, T] {
  return [arg3, arg2, arg1];
}

const result8 = getGenerics("철수", "다람쥐초등학교", 8);

사용자정의 타입이기에 이렇게 축약또한 가능하며

const result8 = getGenerics<string, string, number>("철수", "다람쥐초등학교", 8);

위의 코드의 <string, string, number>처럼 입력값을 기다린 후 입력값으로 통일시키는게 아닌
미리 명시또한 가능하다.

2. (...) => Promise<void>

void는 any타입과 반대로 어떠한 타입도 가지지않는 비어있는 상태를 의미하며 return type이 존재하지 않는다. 하지만 async함수 처럼 비동기 처리를 하는 함수의 return type에 대해선
Promise<void> 를 선언해줘야한다.

3. any, unKnown

TS를 사용하게 되면 타입을 모를경우 any 라는 type를 자주 사용하게 된다.
하지만 TS의 목적은 타입을 명시해줌에 있기 때문에 any를 가급적 사용하지 않는게 좋다.

그렇기 때문에 any 보다는 unknown이 사용 되는데 아래의 코드를 봐보자

// 1. any 타입 (그냥 자바스크립트랑 같음)
const getAny = (args: any) =>{
    return args +2
}
const reuslt = getAny("철수")


// 2. unknown 타입 ()
const getUnknown = (args: unknown) =>{
    if(typeof args === "number"){
        return args+2
    } else{
        return "숫자를 넣어주세요!!!"
    }
    
}

const result2 = getUnknown("철수")

2번 unknown 타입을 보면 함수 안쪽에 타입에 대한 명시를 한번 더 해줬다.
위처럼 타입을 모를때는 any 보단 unknown을 사용하여 원하는 결과에 해당하는 type을 유추해 명시해주어 사용해주는게 좋다

4. graphql-codeGen(code generator)

설치

Graph QL code generator 은
*.graphql 의 파일에 존재하는 스키마와 쿼리를 바탕으로, 다양한 언어에 맞게 타입, 혹은 코드 자체를 생성해준다.

Frontend의 입장이였던 나는 Backend에서 제작해준 API의 스키마 쿼리를 바탕으로된 types 파일을 사용하게 되었다.

우선 codegen을 사용하기 위해 아래의 과정을 진행하였다.
npm 사용자는 npm i 를 사용해주면 된다.

  • yarn add @graphql-codegen/cli
  • yarn add @graphql-codegen/typescript
  • codegne.yaml 파일 생성
  • schema 수정

codegen.yaml 파일을 생성하여 아래의 코드를 입력해주었다.

schema: '백엔드 api 배포 주소'
generates:
  ./src/commons/types/generated/types.ts:
    plugins:
      - typescript
    # config:
    #   typesPrefix:

스키마(schema)에는 백엔드 API의 배포주소를 입력해주면 된다.

이후 yarn generate명령어를 통해 types.ts를 생성하였다.

사용

설치를 받은 types.ts파일에는 다양한 코드가 존재하는데 크게는 Query / Mutation 부분으로 나뉘게 된다, (아래와 같이 export tpye Query/Mutation에 따라 fetch/mutation 부분의 코드가 생성된다. 사진은 Query만 예시로 가져왔다.)

크게 Query/Mutation으로 나뉜 코드들은 다시 한번 나뉘는데

  • query/mutation field에 대한 type
  • query를 통해 받아온 data의 type
  • ENUM, Input ...

등 다양한 타입으로 나뉜다. 이것들에 대한 사용 예시이다.
useQuery

const { data: schedulesData, refetch } = useQuery<
    Pick<Query, "fetchProjectSchedules">,
    QueryFetchProjectSchedulesArgs
  >(FETCH_PROJECT_SCHEDULES_PROJECTID, {
    variables: {
      projectId: String(router.query.projectId),
    },
  });

Pick을 통해 Query중 필요한 부분인 "fetchProjectSchedules"만 가져와 필요한 Args를 호출하며
field가 별도로 존재하지 않는 경우엔 작성해줄 필요없다.
mutation(inputTypes)

import CreateQuestionBoardInput
...
const CreateNewQusetionBoard = async (data: CreateQuestionBoardInput) => {
    if (data) {
      try {
        await createQuestionBoard({
          variables: {
            createquestionBoardInput: {
              title: data.title,
              contents: data.contents,
              questionCategory: select,
            },
          },
        });
        setModalContents("문의 등록이 완료되었습니다!");
        setAlertModal(true);
      } catch (error) {
        let message = "Unknown Error";
        if (error instanceof Error) message = error.message;
        setModalContents(message);
        setErrorAlertModal(true);
      }
    }
  };

CreateQuestionBoardInput의 구성은 다음과 같이 되어있다.

type CreateQuestionBoardInput = {
    contents: Scalars["String"];
    questionCategory: Scalars["String"];
    title: Scalars["String"];
}

data
data는 props를 통해 값을 넘길경우 type을 지정할 경우가 많았는데, 이 부분은
categoriesData?: { fetchProcessCategories: Array<ProcessCategory> };
처럼 원하는 부분을 가져와 { ... } 형태로 타입을 지정해줬다.

Array<T>
배열의 generic 형태로 구성 되어있는 부분은 대해 map, filter, forEach등 원소 하나하나를 다루는 메서드를 쓰게 된다면. 아래처럼 generic형태로 받아오는 Element의 타입을 타입으로 지정해줄 수 있다.

categoriesData?: { fetchProcessCategories: Array<ProcessCategory> }; 가 데이터라면

{categoriesData?.fetchProcessCategories.map((el: ProcessCategory )=>(el...))}

보다 더 많은 사용법이 존재하며 일부만 소개를 해보았다.
이처럼 codegen을 사용하게 되면 type에 대해 시간을 아끼고 코드의 가독성을 높일 수 있다.

5. React-Hook-Form(types)

React-Hook-Form은 React에서 Form 형식의 관리를 도와주는 라이브러리 이다.
라이브러리의 특성상 직접적으로 type을 주는게 힘들어 docs를 참고하는 경우가 많은데,

React-Hook-Form 역시 Typescript를 제공하기에 별도의 ts docs가 존재한다.
ts docs : https://react-hook-form.com/ts

하지만 docs가 있다해도 특정 부분에 대해선 잘 이해가 안되기도 하였고, Container/Presenter 형태의 폴더구조를 사용했기에 props를 넘기는 과정에서
난항도 겪었었기에 작성해보았다.

Props

아래는 React-Hook-Form의 기본적인 구조이다.
값을 받아오는 register
조건에 대해 콜백 형태로 함수를 실행하는 handlsSubmit 가 기본이며,

이 외에도 setValue,getValue,reset등등 여러가지가 있지만 type형태는 비슷하기에 생략한다.

const { register, handleSubmit } = useForm({
    mode: "onChange",
  });

register등 form 에서 지원하는 함수들에 대해선 아래처럼 import를 하여
지원해주는 함수를 사용하면 된다. FormValues / FieldValues 차이 정도만 사용해본 것 같다.

import {
  FieldValues,
  UseFormHandleSubmit,
  UseFormRegister,
  UseFormStateReturn,
} from "react-hook-form";

export interface App {
  handleSubmit: UseFormHandleSubmit<FieldValues>;
  register: UseFormRegister<FieldValues>;
  formState: UseFormStateReturn<FieldValues>;
}

하지만 props를 사용해서 presenter으로 그대로 넘기게 되면 문제가 발생하는데
props.handleSubmit()이 받아오는 함수

<form
  onSubmit={props.handleSubmit(
    props.CreateNewSchedule
   )}
>

에 대해서

 '{ [x: string]: any; }' 형식에 'CreateQuestionBoardInput' 형식의
 ... 속성이 없습니다.ts(2345)

같은 ts error이 발생한다.
이 부분은 콜백 형태로 받아오는 함수 이기에 생기는 error인데

이 부분에 대해선

onSubmit={props.handleSubmit(
  props.CreateNewQusetionBoard as unknown as () => void
)}

같은 방법으로 type을 지정해 주었다.
unknown 을 준 이유는 Props.handleSubmit: <unknown>(onValid: SubmitHandler<FieldValues>
형태의 generic 제공하기 떄문이다.

profile
프론트엔드 주니어 개발자(React, Next.js)

0개의 댓글