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
로 설정하면 import
및 export
문은 원본 파일에 있는 그대로 유지된다고 하여 나는 ES2015
로 설정해주었다.
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015", // here
"strict": true,
"outDir": "dist",
"sourceMap": true
}
}
예전 과제에서는 함수로 컴포넌트를 전부 만들었는데, 클래스로 구현을 다시 해보았다. 클래스가 어렵다고 느껴지기도 했었고, 함수가 더 익숙했기 때문에 함수로 구현했다.
그런데 과제를 먼저 시작한 팀원들이 함수 컴포넌트에 타입을 지정하기가 너무 까다롭다는(this
나 new.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
은 나는 투두 아이템과 아이템 개수를 따로 관리하고 있기 때문에 두 번째 매개변수 값에 따라 반환값이 달라지기 때문에 제네릭을 사용해야 했다.
제네릭을 처음 사용해보았는데 뭔가 상황에 맞게 사용했다는 것만으로도 내심 뿌듯 ..
바닐라 자바스크립트에서 순수 타입스크립트로 마이그레이션하는 것이 까다롭고 차라리 새로 프로젝트를 만드는게 낫다라는 말을 많이 들었는데, 간단한 실습 과제로 진행한 덕에 수월하게 한 것 같았다.
이제껏 진행한 프로젝트를 전부 타입스크립트로 변환해보기로 했는데 틈틈이 복습도 하고, 강의도 다시 들으며 개념 정립을 다시 할 예정이다. 타입스크립트를 자유자재로 쓸 날이 얼른 왔으면 좋겠다.!