이 글은 실전 프로젝트로 배우는 타입스크립트의 최종 프로젝트를 보고 정리한 글입니다.
참고 사이트: https://joshua1988.github.io/ts/etc/convert-js-to-ts.html
기능적인 변경은 절대 하지 않을 것
→ 타입 시스템 적용 후 진행
테스트 커버리지가 낮을 땐 함부로 타입스크립트를 적용하지 않을 것
처음부터 타입을 엄격하게 적용하지 않을 것 (점진적으로 strict 레벨을 증가)
npm init -y
yarn add -D typescript
{
"compilerOptions": {
// 자바스크립트 파일을 그대로 인식해서 사용하겠다.
"allowJs": true,
// 타입스크립트 파일을 특정 버전의 js로 변환해주는 것
"target": "ES5",
// tsc 이후의 결과물이 어디에 저장될 지 알려주는 것
"outDir": "./built",
// Promise 관련 설정
"moduleResolution": "Node"
},
"include": ["./src/**/*"]
}
app.js
→ app.ts
tsc
명령어로 타입스크립트 컴파일하기package.json
"scripts": {
"build": "tsc"
},
위와 같은 명령어 추가 후에
npm run build
명령어 입력하면 타입스크립트로 컴파일 됨
{
"compilerOptions": {
// 자바스크립트 파일을 그대로 인식해서 사용하겠다.
"allowJs": true,
...
}
tsconfig.json
의 파일에서 allowJs와 같은 설정을 true로 만들어 두면app.ts
혹은app.js
를 tsc로 컴파일하였을 때, JS코드로도 빌드가 가능하기 때문에 점진적으로 코드에 타입스크립트 적용을 할 수 있다.
프로젝트에 테스트 코드가 있다면 테스트 코드 동작 확인
프로젝트의 js
파일을 모두 ts
파일로 변경하기
타입스크립트 컴파일 에러가 나는 것 위주로만 먼저 에러가 나지 않게 수정
테스트 코드 성공하는 지 확인
tscofnig.json
에 다음의 설정을 추가한다.
{
...
"noImplicitAny": true
...
}
이후 엄청나게 많은 에러를 확인할 수 있다.
모든 타입에 대해 명시적으로 최소한의any
타입이라도 작성해주어야 함.
→ app.ts
안에 있는 모든 함수의 파라미터(에러가 발생하는 부분)에 any
타입을 적용하여 실습!
// utils
function $(selector: string) {
return document.querySelector(selector);
}
function getUnixTimestamp(date: Date) {
return new Date(date).getTime();
}
위의 쿼리셀렉터로 dom 요소를 가져오는 selector의 경우 문자열이 들어오기 때문에 string, Date를 받아 time을 리턴하는 함수의 파라미터는 Date 타입을 명시해준다.
// DOM
const confirmedTotal = $('.confirmed-total') as HTMLSpanElement;
const deathsTotal = $('.deaths') as HTMLParagraphElement;
const recoveredTotal = $('.recovered') as HTMLParagraphElement;
const lastUpdatedTime = $('.last-updated-time') as HTMLParagraphElement;
위와 같이 DOM을 가져오는 경우 오른쪽의 값의 결과의 타입이 무엇인가를 정의해야함. 따라서 타입 단언을 사용해야 한다.
변수에 타입을 정의해주는 것이 아닌 타입 단언을 사용하여 뒤에다가 타입을 명시해준다.
중간에서 api를 호출하는 함수가 있었는데 이 함수의 파라미터는 api docs 에서 확인을 하여 어떤 파라미터를 전달받는 지 확인하여 타입을 명시해준다.
enum CovidStatus {
Confirmed = ' confirmed',
Recovered = 'recovered',
Deaths = 'deaths',
}
function fetchCountryInfo(countryCode: string, status: CovidStatus) {
// params: confirmed, recovered, deaths
const url = `https://api.covid19api.com/country/${countryCode}/status/${status}`;
return axios.get(url);
}
status와 같은 상태의 경우 이넘을 활용하여 타입을 정의하면 가독성 좋게 사용할 수 있다.
라이브러리 설치
npm i -D typescript @babel/core @babel/preset-env @babel/preset-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint prettier eslint-plugin-prettier
devDependencies → 개발용 라이브러리
dependencies → 배포용 라이브러리
바벨, eslint, prettier 설정 → 강의 자료 참고
현재 위와 같이 cdn으로 갖고오고 있는데 npm 으로 설치하여 import 하여 app.ts
에 적용시켜야 한다.
axios 라이브러리 설치
npm i axios
chart 라이브러리 설치
npm i chart.js
chart.js가 3버전으로 올라가면서 별도의 타입 라이브러리를 설치하지 않아도 됨
app.ts
import axios from 'axios';
import { Chart } from 'chart.js';
타입스크립트에 외부 라이브러리 타입을 알려주기 위해 아래와 같은 작업을 수행해야 한다.
tsconfig.json
{
"compilerOptions": {
...
"typesRoots": ["./node_modules/@types", "./types"]
...
}
}
이 설정을 하게 되면 작성한 폴더 위치로 외부 라이브러리들의 타입을 찾도록 만들어 준다.
이후
types/chart.js/index.d.ts
declare module 'chart.js';
위와 같은 경로로
index.d.ts
라는 타입이 선언된 코드를 모아둔 파일을 만들어 명시해주면 에러를 해결할 수 있다.
→ 이후 최종프로젝트 실습 진행하면서 타입 적용
tsconfig.json
{
"compilerOptions": {
...
"strict": true
...
},
}
엄격한 타입 적용을 하기 위해 위와 같은 설정을 적어주어야 한다.
// deathsList는 DOM 요소
deathsList!.appendChild(li);
다음과 같은 타입 assertion(non-null assertion) 코드를 작성하면 eslint에서 경고를 하는데 이것이 왜 위험한지 알아보자.
interface Hero {
name: string;
skill: string;
}
// const Capt: Hero = {
// name: 'capt',
// skill: 'shield',
// };
// const capt: Hero = {};
const capt = {} as Hero;
// capt.name = 'capt';
// capt.skill = 'shield';
이와 같은 코드에서
capt
라는 변수는 Hero 인터페이스를 따르는 변수인데 빈 객체로 선언하여 Hero를 타입 단언을 해버리면 타입스크립트 에러가 발생하지 않는다.
이러한 상황에서name, skill
과 같은 내부 프로퍼티들을 적용하지 않아도 에러를 발생시키지 않기 때문에 코드를 위험하게 작성하게 되는 것이다.
따라서 아래의 non-null assertion또한 사용 시 보이지 않는 사각지대에서 에러를 발생시킬 수 있기 때무네 유의해서 사용해야 한다!
const a: string | null;
a!;
DOM 엘리먼트를 가져오는 부분에서 보통 as와 타입을 이용하여 타입 단언을 통해 null 에러를 발생시키지 않도록 한다.
// utils
function $(selector: string) {
return document.querySelector(selector);
}
const confirmedTotal = $('.confirmed-total') as HTMLSpanElement;
const deathsTotal = $('.deaths') as HTMLParagraphElement;
// utils
function $<T extends HTMLElement>(selector: string) {
const element = document.querySelector(selector);
return element as T;
}
const confirmedTotal = $<HTMLSpanElement>('.confirmed-total');
const deathsTotal = $<HTMLParagraphElement>('.deaths');
위와 같이 작성하여 타입 단언을 위한 인수를 제네릭으로 전달하여 사용하면 더 활용성을 높인 함수를 만들 수 있다.