타입스크립트 적용기

Sinf·2022년 1월 22일
0

고민의 흔적

목록 보기
6/38
post-thumbnail

타입스크립트 적용기

1차프로젝트 다시보기

다시 열어본 코드에서 포스팅한 것과 같이 1차 프로젝트 이후 코드를 다시 열어봤다.

호오... 다시 짤까? 라는 생각이 들 정도로 다시 알아보기 어려운 코드였다. 하지만, 문제는 해결하라고 있는 법.

먼저, 가장 필요한 것에 대해 고민했다.
그리고 정했다. 타입스크립트.

왜 타입스크립트?

자바스크립트를 사용하면서 고민되는 것이 많았다.
테스트 코드를 통해 코드의 신뢰성을 얻고자 했지만, 프로젝트를 진행하면서 스멀스멀 올라오는 불안감을 본능적으로 느끼고 있었다.

익명의 개발자: 씬프님, 이거 response 어떻게 날라오나요?
씬프: 어 잠시만요. (API 명세를 확인한다. 하지만, 업데이트 한지 오래되었다.)
   음...  진짜 잠시만요...

열심히, 테스트 코드에 콘솔로그를 찍어 response에 담긴 데이터를 확인하곤 했다.

나의 생각

사실, 이것을 타입스크립트로 해결한다? 라고 하면 확실하게 말하진 못하겠다. 하지만 내가 공부하면서 타입스크립트의 타입 추론으로 더 쉽게 확인할 수 있지 않을까? 라는 생각을 했다.

뿐만 아니라 파라미터의 타입, 리턴의 타입, 객체 프로퍼티에 대한 추론 등 타입스크립트를 사용하는게 더 안정적일 것 같다는 생각을 하게 되었다.

물론, 다른 언어를 사용하라고 하실 수 있지만, 자바스크립트라는 언어가 가진 특성이 정말 재밌어서 이를 활용하고 싶었다.

타입스크립트 적용기

먼저, 이미 많은 코드를 가진 프로젝트를 바꾸면서 공부하기에는 어려웠다. 그래서 CRUD 기능을 가진 게시판을 만들면서 타입스크립트를 적용하기로 했다.

타입스크립트 설정

tsc --init

감사하게도, 프로젝트 경로에서 명령어를 입력하면, tsconfig.json을 생성해준다. 아주 친절하게도, 모든 설정이 주석까지 달려서 포함되어있다. 필요에 따라 주석해제하고 사용하면 된다.

하지만, 너무 많은 설정 목록으로 어질어질하다. 나같은 타린이에겐 너무 어려워...

microsoft/TypeScript-Node-Starter에서 어떤 설정을 사용하는지, 무슨 설정인지 확인하면서 가져와 사용하게 되었다.

@types

타입스크립트를 사용하려면 내가 사용하는 패키지들의 타입들이 정리된 패키지가 필요하다. @types/XXX 와 같은 형태로 패키지를 설치하면 타입 패키지를 설치할 수 있다.

가자 프로젝트로

타입스크립트를 익숙하게 다루기 위해 반복했다.
이제 프로젝트에 적용해보자!!

나는 한 치 앞도 모를 코드를 작성해왔다

프로젝트에서 라이브러리를 가져와 사용할 때, 그 내부에 대한 확인을 했을까? 기능만 가져와 사용하기 바빴다. 타입스크립트를 적용하면서 내 코드에 대한 불안감이 체감되기 시작했다.

JWT 토큰을 생성할 때

자바스크립트를 사용할 때, 특정 라이브러리의 옵션을 쉽게 정의해 던진 적이 많다. 하나의 예를 들어, JWT을 생성하는 로직을 본다면,

  const token = jwt.sign(
    {
      exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7,
      data: payload,
    },
    process.env.JWT_SECRET,
  );

JWT 토큰을 만드는 로직을 그저 인터넷에 돌아다니는 코드를 가져다가 사용했다. 문제 없이 사용되기에 좋다! 하면서 사용했다. 타입스크립트를 적용하고 난 뒤, 제대로 동작하지 않는다. 토큰을 생성할 때 사용하는 sign 함수의 정의를 열어보기 시작했다.

export function sign(
    payload: string | Buffer | object,
    secretOrPrivateKey: Secret,
    options?: SignOptions,
): string;

sign은 콜백함수 없이 사용할 때, 위와 같이 2개의 필수인자, 그리고 선택적으로 옵션을 받는다. SignOptions에 대한 정의는 어떻게 되어 있을까?

export interface SignOptions {
    algorithm?: Algorithm | undefined;
    keyid?: string | undefined;
    expiresIn?: string | number | undefined;
    notBefore?: string | number | undefined;
    audience?: string | string[] | undefined;
    subject?: string | undefined;
    issuer?: string | undefined;
    jwtid?: string | undefined;
    mutatePayload?: boolean | undefined;
    noTimestamp?: boolean | undefined;
    header?: JwtHeader | undefined;
    encoding?: string | undefined;
}

다른 것보다도, 여기서 나는 expiresIn을 확인했다.
나는 그동안 payload에 데이터와 만료기간을 넣어 토큰을 생성했다.
하지만, sign 함수는 payload, secret-key, options를 각각 받아 적용한다. 그리고, expiresIn을 사용하면 기간을 계산할 필요없이 문자적 표현으로 만료기간을 정의할 수 있다.

  const payload = { payload };
  const signOpts: SignOptions = {
    expiresIn: "7d",
  };
  const token = jwt.sign(
    payload,
    process.env.JWT_SECRET,
    signOpts
  );

타입스크립트를 사용했을 때, 더 정확하게 코드를 작성하게 된 것 같다.
그리고 내부에서 어떻게 동작하는지 집중하게 된 것 같다.


Multer의 옵션을 적용할 때

파일을 저장하는 라이브러리 Multer에 타입스크립트를 적용하는 것이다.

const upload = multer({
    storage: multer.diskStorage({
      destination(_req, file, cb) {
        cb(null, process.env.UPLOAD_PATH);
      },
      fileName(_req, file, cb) {
        cb(
          null, 
          filename
        );
      },
    }),
    limits: { fileSize: 1024 * 1024 * 20 },
    fileFilter,
  }).array("photos", 4);

multer 함수는 파일을 업로드할 때, 저장할 위치, 파일 이름 등을 옵션으로 전달해 저장할 때 적용할 수 있다.

타입스크립트를 적용한 후, fileName에서 계속 눈엣가시 같은 밑줄이 그어졌다. 🧐🧐🧐🧐🧐. 그대로 볼 수 없었다.

열어본다. d.ts.

interface DiskStorageOptions {
  destination?: string | ((
  req: Request,
  file: Express.Multer.File,
  callback: (error: Error | null, destination: string) => void
  ) => void) | undefined;
  filename?(
  req: Request,
   file: Express.Multer.File,
   callback: (error: Error | null, filename: string) => void
  ): void;
}

filename....? fileName이 아니라...?
간단한 문제였다. 객체의 프로퍼티의 이름을 바꾸고 끝났다.

근데 이게 타입스크립트를 쓰는 이유일까?

타입스크립트를 적용하면서 느낀 점

소개한 예시들 말고도 타입스크립트를 적용하면서 코드를 다시 보게된 것은 많다.

가장 큰 이점은, 자바스크립트에서 그저 그렇게 쉽게 사용하던 코드와 타입들이 사실 내부적으로 어떻게 정의되고 어떤 사용방식으로 만들어졌는지, 알고 사용하게 되었다는 것이다.

monogoose를 사용하면서도 _idid의 차이도 모르고 typeof로 타입체크도 안하고 그저 사용한 것과 같이, 그냥 있으니까 사용한 것들이 많았다.

그러다보니, 코드는 점점 예측할 수 없는 괴로움에 이상해졌다.

if (post.author.toString() !== authorId.toString()) {
  throw new Error("권한이 없습니다.");
}

이 코드는 왜 toString으로 사용할 수 밖에 없었는가...
아무것도 모르고 사용하던 때와 다른 알고 사용함의 쾌감은 👏🏻👏🏻

_id는 object 타입이고, id는 string 타입이다. 코드를 작성할 때, _id, id를 혼용해서 사용하다보니 타입이 맞지 않아 깊은 비교를 할 때 문제가 나와 toString을 적용했다. 왜 그런지도 모르고...

코드를 모르고 짠다는 것은 정말 무섭다.
앞으로 express와 같은 node 기반의 프레임워크를 사용할 땐,
무조건 타입스크립트를 작성할 것이다.

다음 리팩터링

타입스크립트로 변환도 끝났다.
일단, 테스트 코드를 추가로 작성하고 있고, 테스트 코드가 끝난다면,

서비스 레이어에 덕지 덕지 붙은 모델 기능들을 하나씩 떼어내야하지 않을까?

내용은 아주 아주 개인적인 의견입니다.

profile
주니어 개발자입니다. 🚀

0개의 댓글