[ts] 점진적으로 타입스크립트를 적용해 나가는 프로세스 및 프로젝트 환경 구성, 편리한 유지보수를 위한 유틸리티 타입
타입스크립트를 점진적으로 적용해 나가기
1
2
3
4
5
(1) 타입스크립트 환경 구성
(2) 명시적인 any 선언
(3) 구체적인 타입 정의
(4) 외부 라이브러리 모듈화
(5) 'strict' 옵션 추가 후 타입 정의
타입스크립트 환경 구성
typescript
라이브러리를 설치하고 관리하기 위해 우선, npm 초기화부터 수행하여,
🧆 package.json
파일을 생성한다.
※ package.json에 커스텀 명령어 “build”: “tsc” 지정
- 🍍 package.json
- nodejs에서 사용하는 모듈들을 패키지로 만들어 관리하고 배포하는 역할을 한다.
typescript
라이브러리를 개발용으로 설치한다.
1
2
npm init -y
npm i -D typescript
타입스크립트 설정 파일 🎳 tsconfig.json
을 생성한다.
1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {
"allowJs": true,
"target": "ES5",
"outDir": "./dist",
"moduleResolution": "node",
"lib": ["ES2015", "DOM", "DOM.Iterable"]
},
"include": ["./src/**/*"]
}
- 🏴
"allowJs": true
- js까지 ts가 검증
- ts를 점진적으로 적용하기 위해 js까지 컴파일한다.
- 🏴
"outDir": "./dist"
- ts의 결과물이 어디에 들어갈 것인지를 지정
- 🏴
"moduleResolution": "node"
- Promise를 인식시켜 주기 위해 설정
- 🏴
"include": ["./src/**/*"]
- 어떤 파일을 대상으로 ts파일을 컴파일 할 것인지 지정
["**/*"], default
모든 폴더 밑에 있는 모든 파일을 대상으로 - 🏴
"exclude": ["node_modules", "bower_components", "json_packages"]
- 어떤 파일을 ts 컴파일에서 제외 할 것인지 지정,
default
해당 환경을 구성한 뒤에,
js파일을 하나씩 ts파일로 변환하고 빌드해본다.
1
npm run build
타입에러가 났음에도 js파일이 생성된다.
즉, 타입에러와 런타임에러는 서로 독립적인 관계이다.
명시적인 any 선언
🎳 tsconfig.json
파일에 noImplicitAny: true
옵션을 추가한다.
우선 에러를 해결한다는 관점에서만 에러가 난 부분에 명시적으로 any타입을 정의해준다.
※ 타입을 정하기 어려운 곳이 있으면 명시적으로 any롤 선언한다.
외부 라이브러리 모듈화 axios, chart.js
- 🍍 axios
- Promise based HTTP client for the browser and node.js
npm i axios
- 🍪 타입스크립트가 외부 라이브러리(모듈)를 해석하는 방식
- js 라이브러리를 ts에 이식하려면 ts가 인식할 수 있게 중간에 타입을 정의해줘야 한다.
즉, 타입 정의 파일(index.d.ts)
이 있어야 한다. d: declaration type, 선언파일- (방법1) 라이브러리 내부 자체에 있는 경우, 해당 폴더 하위의
index.d.ts
파일 참조
- (방법2) 타입 정의 라이브러리가 따로 있는 경우, 해당 라이브러리를 설치 in
Definitely Typed
node_modules
아래@types
하위 폴더에 정의된index.d.ts
파일 참조
(방법3) 타입 정의 라이브러리가 제공되지 않는 경우,
index.d.ts
파일 직접 정의
🎳 tsconfig.json
에"typeRoot": ["./node_modules/@types", "./types"]
지정
./types
하위에 라이브러리명으로 폴더를 만들고index.d.ts
파일 정의ex. ./types/chart.js/index.d.ts
1
declare module 'chart.js';
※ 옛 버전의 chart.js에는 라이브러리 내부 자체 index.d.ts 파일이 없었다.
- (방법4) vue3의
DefineComponent
이용
ex. type QuillEditorType = DefineComponent<typeof QuillEditor>;
DefineComponent
는 vue 컴포넌트의 타입을 명확히 지정할 때 사용된다.
typeof QuillEditor
는 QuillEditor라는 외부 API 객체의 런타임 타입을 가져온다.
즉, 해당 런타임 타입을 가져와 vue 컴포넌트의 타입을 명확하게 지정하여 quillEditor의 타입을 지정할 수 있다.
The repository for high quality
Typescript type definitions.
외부 라이브러리에 대해 이미 만들어논 오픈소스 타입 정의 라이브러리 저장소
라이브러리 내부 자체에 타입정의 파일 index.d.ts이 없는 경우
Definitely Typed 저장소의 @types에서 관련 타입정의 라이브러리를 찾아 설치한다.
@types : Definitely Typed 저장소 하위에 정의된 타이핑(typing) 라이브러리
strict 옵션 추가 후 타입 정의
🎳 tsconfig.json
파일에 "strict": true
옵션 추가
좀 더 엄격하게 타입을 정의할 수 있게 동작을 점검한다.
🎯 추후에 일어날 수 있는 타입정의에 대한 오류를 막을 수 있다.
해당 옵션을 추가하면 아래의 옵션들을 추가한 효과가 생긴다.
1
2
3
4
5
6
"alwaysStrict": true
"strictNullChecks": true
"strictBindCallApply": true
"strictFunctionTypes": true
"strictPropertyInitialization": true
"noImplicitThis": true
strictNullChecks
- 🍪 해당 에러를 해결하는 4가지 방법
(1)
type guard
1 2 3 4 5 6 7
const div = document.querySelector('div'); funciton clearDiv() { if(!div) { return; } div.innerHTML = ''; }
type assertions
- ⚠️ 확신이 있을 때만 사용해야한다. 타입에러가 나지 않고, 객체의 경우 필요한 속성을 누락할 수 있다.
(2)as
(3)non-null type assertion
: !
(4)optional chaining operator
: ? 권고1 2 3 4 5 6 7 8 9 10 11 12 13 14
// (1) const div = document.querySelector('div') as HTMLDivElement; // (2) !: null이 아니다 div!.innerHTML =''; // (3) div?.innerHTML = ''; // 내부적으로 해당 동작과 같다 // if (div === null || div === undefined) { // return; // } else { // div.innerHTML = ''; // }
📕 타입스크립트 내부적으로 정의된 타입 간의 상하 관계
HTMLDivElement extends HTMLElement extends Element
MouseEvent extends UIEvent extends Event
strictFunctionTypes
- 🐖 No overload matches this call.
정의된 함수의 스펙과 넘겨받은 함수의 스펙이 일치하지 않을 때 해당 에러 발생
예제: DOM API 관련 유틸리티 함수 선언
1
2
3
4
5
6
7
8
9
10
11
12
function getUnixTimestamp(date: string | Date) {
return new Date(date).getTime();
}
funtion $<T extends HTMLElement = HTMLDiveElement>(selector: string) {
const element = document.querySelector(selector);
return element as T;
}
// Generic으로 타입까지 넘겨받으면 해당 함수 안에서 type assertion까지 할 수 있다.
// 넘겨받는 타입이 없을 경우를 대비하여, default타입을 위와 같이 정의할 수 있다.
const canvas = $<HTMLCanvasElement>('.canvasEle');
Utility Type
🎯 유틸리티 타입을 이용하면 불필요하게 중복되는 타입 코드들을 줄여나갈 수 있다.
Partial
- 특정 타입의 부분집합을 만족하는 타입을 정의할 수 있다.
1 2 3 4 5 6 7 8 9
interface Address { email: string; address: string; } type MayHaveEmail = Partial<Address>; const me: MayHaveEmail = {}; // (ㅇ) const you: MayHaveEmail = { email: "abc@abc.com" }; // (ㅇ) const all: MayHaveEmail = { email: "peter@abc.com", address: "Seoul" }; // (ㅇ)
Pick
- 특정 타입에서 지정된 속성만 골라 타입을 정의
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
interface Product { id: number; name: string; price: number; brand: string; stock: number; } interface ProductOutline { id: number; name: string; price: number; } type ShoppingItem = Pick<Produce, "id" | "name" | "price">;
Omit
- 특정 타입에서 지정된 속성만 제거한 타입을 정의
1 2 3 4 5 6 7 8 9 10 11
interface AddressBook { name: string; phone: number; address: string; company: string; } const chingtao: Omit<AddressBook, "address" | "company"> = { name: "중국집", phone: "1234123412" };
Mapped Type
기존에 정의된 타입을 변환할 때 사용하는 문법인데,
js의 map API
함수를 타입에 적용한 것과 같은 효과를 가진다.
🎯 내부 구현체로 많이 활용된다.
🍪 Mapped Type 기본 문법
1
2
3
4
{ [ P in K ]: T }
{ [ P in K ]?: T }
{ readonly [ P in K ]: T}
{ readonly [ P in K ]?: T }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Person = "Peter" | "Jack" | "Lami";
type PersonAges = { [K in Person]: number };
// 기존에 정의된 타입을 Mapped Type 문법을 이용해 새로운 타입으로 변환(?)
// 아래와 동일한 타입 코드이다
// type PersonAges = {
// Peter: number;
// Jack: number;
// Lami: number;
// }
const participantsAges: PersonAges = {
Peter: 30,
Jack: 45,
Lami: 27
};
Utility Type 내부동작 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
type UserProfileUpdate = Particial<UserProfile>;
// 아래와 동일한 타입코드이다.
// type UserProfileUpdate = {
// username?: string;
// email?: string;
// profilePhotoUrl?: string;
// }
// #1
type UserProfileUpdate = {
username?: UserProfile["username"];
email?: UserProfile["email"];
profilePhotoUrl?: UserProfile["profilePhotoUrl"];
};
// #2, Mapped Type 문법 이용
type UserProfileUpdate = {
[p in "username" | "email" | "profilePhotoUrl"]?: UserProfile[p];
};
// #3, keyof 문법 이용
type UserProfileUpdate = {
[p in keyof UserProfile]?: UserProfile[p];
};
// #3, 기존에 정의된 다른 타입을 넘겨받을 수 있게, Generic 이용
type Subset<T> = {
[p in keyof T]?: T[p];
};
// Partial은 내부적으로 이렇게 정의되어 있다.