[회고] Todo List 타입스크립트 마이그레이션 과제

yoon·2023년 12월 9일
0

10월쯤 바닐라 자바스크립트로 간단한 투두리스트 앱 만들기 실습을 한 적이 있었다.
이번에 타입스크립트 스터디를 진행하면서 진행했던 과제들을 하나씩 타입스크립트로 변환해보려고 한다.
바닐라 자바스크립트에서 순수 타입스크립트로 세팅부터 함수에서 클래스로 변환하는 것까지 경험하며 겪은 에러나 느낀점들을 소소하게 풀어볼 생각이다.

세팅

세팅은 이 블로그를 참고했다. 이후에 파일 확장자를 .ts로 변경했다.

// tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "outDir": "dist",
    "sourceMap": true
  }
}

tsc는 TypeScript 컴파일러인데 이를 이용해서 자바스크립트 파일로 변환할 수 있다. tsc 명령어를 이용해 컴파일 결과의 JavaScript 파일들이 dist폴더에 생길 것이고, sourcemap도 생긴다. watch mode인 명령어 npx tsc -w로 컴파일을 시작하면 자동으로 TypeScript 파일에 변화가 있을 때마다 다시 컴파일을 해준다.

나는 watch mode를 켜두니 상관이 없지만 팀원이나 다른 사람들이 돌려볼 것을 위해 package.json 파일에 scripts 명령어를 다음과 같이 했다.

"scripts": {
    "start": "tsc && npx serve"
},

이렇게 하면 컴파일과 함께 실행이 되기 때문에 index.html에서

<script src="/dist/main.js" type="module"></script>

불러오는 파일에 바로 접근할 수 있다.

Uncaught ReferenceError: exports is not defined

tsc 명령어를 통해 컴파일된 .js 파일을 불러왔는데 이 에러가 떴다. 검색해보니 브라우저는 commonjs 모듈을 지원하지 않기 때문에 컴파일 후 모듈을 번들링 하려면 일부 도구(웹팩, 롤업, 브라우저화)를 사용해야 한다고 했다. 따라서 tsconfig.json 에서 module 옵션을 제거하거나 es2015 또는 esnext 로 설정하면 importexport 문은 원본 파일에 있는 그대로 유지된다고 하여 나는 ES2015로 설정해주었다.

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ES2015", // here
    "strict": true,
    "outDir": "dist",
    "sourceMap": true
  }
}

함수에서 클래스로 변환

예전 과제에서는 함수로 컴포넌트를 전부 만들었는데, 클래스로 구현을 다시 해보았다. 클래스가 어렵다고 느껴지기도 했었고, 함수가 더 익숙했기 때문에 함수로 구현했다.

그런데 과제를 먼저 시작한 팀원들이 함수 컴포넌트에 타입을 지정하기가 너무 까다롭다는(thisnew.target 같은 ..) 평을 받고 이왕 클래스로 변환을 도전해보자는 다짐을 했다. 타입스크립트 스터디이기 때문에 팀원들이 맞닥뜨린 문제를 해결해보는 것도 좋지만, 나는 아직 타입스크립트가 너무 어렵다고 생각하고, 개념이 확실히 잡힌 상태가 아니기 때문에 내가 감당할 수 있는 챌린지부터 해보고 싶었다.

클래스 생성자 메서드 constructor

객체의 기본 상태를 설정해주는 생성자 메서드 constructor는 인스턴스를 생성하고 초기화하는 메서드다. new에 의해 자동으로 호출되므로, 특별한 절차 없이 객체를 초기화 할 수 있다.

따라서 초기에도 렌더링 되도록 render 메서드도 constructor 내부에 호출해주고, 첫 렌더링 때 필요한 로직들(ex. 앨리먼트 생성 및 삽입, 이벤트 등록 등) 또한 적어주었다.

class TodoList {
  state: TodoItem[] = [];
  private readonly $todoList = document.createElement("div");

  constructor(
    private readonly $target: HTMLElement,
    private readonly initialState: TodoItem[],
    private readonly updateCount: (state: TodoItem[]) => void
  ) {
    this.$target.appendChild(this.$todoList);
    this.render();
    this.$todoList.addEventListener("click", (e) => {
      // ...code
    });
  }

  setState(nextState: TodoItem[]) {
    // ... code
    this.state = nextState;
    this.render();
  };

  private render() {
    this.$todoList.innerHTML = `...code`;
  };
}

타입스크립트에서는 constructor 메서드의 매개변수를 자동으로 this에 바인딩해주어 좋았다. 또한 constructor 메서드 내부에서는 this를 통해 접근하지 않아도 된다. 그러나 나는 통일성을 위해 전부 this 키워드를 적었다. constructor 메서드 내부에서 로직이 끝난다면 this 키워드 없이 적어도 깔끔할 것 같다.

제네릭

export const getItem = <T>(key: string, defaultValue: T): T => {
  try {
    const data = storage.getItem(key);
    if (data) return JSON.parse(data);
    return defaultValue;
  } catch (error) {
    console.log(error);
    return defaultValue;
  }
};

getItem은 나는 투두 아이템과 아이템 개수를 따로 관리하고 있기 때문에 두 번째 매개변수 값에 따라 반환값이 달라지기 때문에 제네릭을 사용해야 했다.
제네릭을 처음 사용해보았는데 뭔가 상황에 맞게 사용했다는 것만으로도 내심 뿌듯 ..

회고

바닐라 자바스크립트에서 순수 타입스크립트로 마이그레이션하는 것이 까다롭고 차라리 새로 프로젝트를 만드는게 낫다라는 말을 많이 들었는데, 간단한 실습 과제로 진행한 덕에 수월하게 한 것 같았다.
이제껏 진행한 프로젝트를 전부 타입스크립트로 변환해보기로 했는데 틈틈이 복습도 하고, 강의도 다시 들으며 개념 정립을 다시 할 예정이다. 타입스크립트를 자유자재로 쓸 날이 얼른 왔으면 좋겠다.!

profile
얼레벌레 개발자

0개의 댓글