TypeScript를 잘 쓴다는 건 무엇일까

.Code Pathfinder·2025년 2월 9일
1

diary

목록 보기
2/2
post-thumbnail

나는 TypeScript를 왜 사용하느냐에 대한 질문에 형식적인 답을 했었는데,
이제는 내가 경험한 것을 중심으로 답할 수 있다.

TypeScript를 왜 사용하느냐고 물어본다면, 이제 나는 이렇게 답한다

  • 데이터 타입을 정의하여 런타임에서 발생할 수 도 있는 버그를 줄여주고,
  • 데이터 사이의 관계들을 명확하게 하기 위함이라고

내가 내린 ‘TypeScript를 잘 쓰는 것’의 정의는 < > 이다.

  • 기존 타입을 재사용하여 중복을 줄이는 것
  • 집합적으로 타입을 정의하여 데이터 사이의 관계를 쫀쫀하게 만드는 것


과거로 흘러가보자.

프로그래밍 구현에 먼저 신경쓰다보니 타입 정의에 관한 부분을 조금 소홀히 했다.
types.ts 파일을 살펴보니 내가 작성한 타입은 원시 타입만 줄줄@@ 작성돼 있었다.
아무리봐도… 너무 똑같은 타입들이 많았다.

export interface BookType {
  author: string;
  description: string;
  diaries: { [key: string]: boolean };
  isbn: string;
  image: string;
  title: string;
  link: string;
}
export interface SelectedBookType {
  author: string;
  description: string;
  image: string;
  isbn: string;
  title: string;
  link: string;
}
export interface AllDiariesType {
  diary: {
    bookImage: string;
    bookTitle: string;
    diaryId: string;
    diaryImage: string;
    diaryTitle: string;
  };
  post: {
    content: string;
    postId: string;
    title: string;
  };
  user: {
    avatar: string;
    userId: string;
    username: string;
  };
}

이때부터 였다.

"나는 TypeScript를 쓰고 있다고 말할 수 있는걸까?"
"TypeScript를 잘 쓴다는 건 무슨 뜻일까?"라는 궁금증이 생겼다.

위 코드처럼, string과 같은 원시타입을 남발하는 것이…
TypeScript를 사용하고 있다고 말할 수 있는건가… 하는 생각이 들었다.
어떻게 하면 TypeScript를 잘 썼다고 말할 수 있을까?
하는 고민과 함께 Effective TypeScript 책을 다시 훑어보기 시작했다.

다시 본 TypeScript의 핵심 정의는 다음과 같았다

  • TypeScript는 JavaScript의 프리셋 = 상위 호환 언어다.
  • TypeScript의 타입 시스템은 JavaScript의 런타임을 모델링해 동작한다.
    ( JavaScript의 코드를 모두 실행할 수 있다 )
  • 타입 체크와 런타임 동작은 서로 구분돼 실행된다.
    ( 타입 에러가 발생해도 화면이 정상적으로 동작되는 이유 )
  • 덕 타이핑구조 ( 타입간의 집합 관계를 이루고 있다 )

이를 기반으로 타입정의를 조금씩 리팩토링하였다.

함수 표현식에 타입 지정해 한눈에 알아보기 쉽게 하기

firebase와 호출하는 함수의 작성이 많아질수록 내용이 한눈에 들어오기 어렵다. 더불어 TypeScript를 사용하고 있는 나로서는 함수 내부에 타입까지 작성돼 있다면 이 함수가 어떤 인자를 받고 어떤 내용의 함수이며 어떤 데이터를 반환하는지 헷갈리기까지 한다.

// 변경 전
export const getBookData = async (bookId: string): Promise<BookType | null> => { ... }
export const updateDiaryLikeState = async (
  likeState: boolean,
  diaryId: string,
  userId: string
) => { ...  };

export const likeSubscribe = (
  diaryId: string,
  userId: string,
  setLikes: React.Dispatch<React.SetStateAction<boolean>>
) => { ... } 

export const updateDiary = async (
{ diaryId,  newTitle } : {  diaryId: string;  newTitle: string; }) 
=> { ... }

Effective TypeScript에서 많은 아이디어를 얻었다. ( 아이템 12 )

📖 매개변수와 반환값에 타입을 지정하기 보다 함수 표현식 전체에 타입 구문을 적용하라!
반복되는 함수 시그니처에 대한 타입을 만들어 사용하는 것도 좋은 방법이라고 이야기함.

// 매개변수와 반환값에 타입을 지정하기 보다 함수 표현식에 타입을 지정
function add(a: number, b: number) { return a + b; }
const rollDice = (side:number): number => { ... }

functionTypes.ts파일에 firebase와 통신하는 API함수의 타입을 지정하고,
함수표현식에 해당 타입을 지정하였다.
이로써 함수는 어떤 인자를 받아 서버와 통신하는지 한눈에 알아보기 편해졌고,
해당 데이터 타입을 찾아가 인자와 반환값의 타입 정보를 따로 볼 수 있게 되었다.

// 변경 후 
// functionTypes.ts에 함수의 타입 지정
export type GetBookDataType = (bookId: string) => Promise<BookType | null>; 
export type GetDiaryDataType = (diaryId: string) => Promise<DiariesType | null>;
export type GetAllPostsDataType = (bookId: string) => Promise<PostsType[] | []>;

export const getBookData: GetBookDataType = async (bookId) => { ... }
export const getDiaryData: GetDiaryDataType = async (diaryId) => { ... } 
export const getAllPostsData: GetAllPostsDataType = async (diaryId) => {


데이터를 집합 관계로 표현하여 타입 중복 줄이기

(1) BookType과 SelectedBookType의 extends 사용

아래의 Book과 SelectedBook 타입은 BookTypes에 diaries를 제외하고는 같은 형태의 데이터 타입이기 때문에 타입 중복이 발생해 이를 줄이는 작업이 필요했다.

export interface SelectedBookType {
  author: string;
  description: string;
  image: string;
  isbn: string;
  title: string;
  link: string;
}
export interface BookType {
  author: string;
  description: string;
  diaries: { [key: string]: boolean };
  isbn: string;
  image: string;
  title: string;
  link: string;
}

extends는 어떨때 사용하나?

BookType은 SelectedBookType의 모든 속성을 상속하면서 추가로 diaries속성을 가진다. SelectedBookType은 책 정보만 담겨있고, BookType은 책 + 다이어리 정보가 추가되는데,
이를 extends로 기존 데이터를 상속해 표현할 수 있다.

extends를 사용하여 SelectedBookType의 타입은 상속하고, diaries타입을 추가한다.
이렇게 하면 중복 선언을 줄일 수 있고,
해당 타입이 어떤 타입과 관계(논리적인 연관성)를 가지고 있는지 더 명확하게 알 수 있다.

아래 코드는 BookType은 SelectedBookType를 extends한 타입이다.

export interface SelectedBookType {
  author: string;
  description: string;
  image: string;
  isbn: string;
  title: string;
  link: string;
}

export interface BookType extends SelectedBookType {
  diaries: { [key: string]: boolean };
}

(2) Pick을 사용해 필요한 타입만 사용하기

AllDiariesType은 여러 db필드에서 데이터를 가져와서 반환값을 만든 데이터다.
diary, post, user의 db에서 데이터를 뽑아왔기 때문에 기본 데이터 타입과 중복이 있다.

db에서 모든 데이터를 합쳐 받아온 것을,
각 db테이블의 이름을 살려 객체 형태로 반환하는 것으로 수정하였다.
동시에 해당 데이터의 기본 타입에서 필요한 타입을 뽑아서
유틸리티 Pick타입으로 타입을 정의해 중복을 줄이기로 했다.

export interface AllDiariesType {
    bookImage: string;
    bookTitle: string;
    diaryId: string;
    diaryImage: string;
    diaryTitle: string;
    content: string;
    postId: string;
    title: string;
    avatar: string;
    userId: string;
    username: string;
  };
}

export interface AllDiariesType {
  diary: {
    bookImage: string;
    bookTitle: string;
    diaryId: string;
    diaryImage: string;
    diaryTitle: string;
  };
  post: {
    content: string;
    postId: string;
    title: string;
  };
  user: {
    avatar: string;
    userId: string;
    username: string;
  };
}

Pick은 무엇인가?

유틸리티 타입의 Pick<T,K>은
기존 타입에서 특정 속성만 선택하여 새로운 타입을 만들때 사용하는 타입으로
이미 정의해둔 diaries / post / user 의 타입에서
필요한 속성만 가져와 새 타입을 지정할 수 있어 유용하게 사용했다.

type Pick<T, K>
// T : 원본 객체 타입
// K : 가져오고 싶은 속성의 키값
export interface AllDiariesType {
  diary: Pick<DiariesType,"bookImage" | "bookTitle" | "diaryTitle" ... >
  post: Pick<PostsType, "content" | "postId" | "title">;
  user: Pick<UserType, "avatar" | "userId" | "username">;
}


TypeScript의 매력이란.

Pick과 extends를 사용하면서
관련 있는 데이터들은 서로 논리적 관계를 맺을 수 있게 지정해 줄 수 있었고,
나 또한 데이터가 확장됨에 따라 해당 데이터들이 어떤 구조로 이루어져 있는지
더 빠르게 파악할 수 있었다.

TypeScript를 사용하며 느낀 점은 아래와 같았다.

  • 기본 데이터와 함께 확장해가는 데이터들의 관계를 표현하기에 편리한 도구
  • 프로젝트의 함수, 데이터를 체계적으로 관리할 수 있도록 해 주는 언어
    => 데이터 사이의 관계를 쫀쫀하게 만들 수 있다는 것.

프로젝트에 타입들을 적용하며 TypeScript의 타입 시스템을 이해할 수 있게 됐고,
더 많은 유틸리티 타입 & 제네릭을 활용해 타입들을 리팩토링 할 계획이다.

profile
코드로 문제를 해결하고 탐험하는 사람

0개의 댓글