타입스크립트에서 가리키는 모듈이라는 개념은 ES6+의 Modules 개념과 유사하다.
모듈은 전역 변수와 구분되는 자체 유효 범위를 가지며 export
, import
와 같은 키워드를 사용하지 않으면 다른 파일에 접근할 수 없다.
ES6의 export
와 같은 방식으로 변수, 함수, 타입, 인터페이스 등에 붙여 사용한다.
// math.ts
export interface Triangle {
width: number;
height: number;
}
// index.ts
import { Triangle } from './math.ts';
class SomeTriangle implements Triangle {
// ...
}
ES6의 import
와 동일한 방식으로 사용한다.
import { WheatBeerClass } from 'index.ts';
class Cloud extends WheatBeerClass {
}
tsconfig.json
파일에 설정한 컴파일러 모드에 따라 모듈 코드가 각기 다르게 변환된다.
// SimpleModule.ts
import m = require('mod');
export let t = m.something + 1
// AMD / RequireJS SimpleModule.js
define(["require", "exports", "./mod"]), function (require, exports, mod_1) {
exports.t = mod_1.something + 1;
}
// CommonJS / Node SimpleModule.js
var mod_1 = require("./mod");
exports.t = mod_1.something + 1;
// UMD SimpleModule.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports); if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./mod"], factory);
}
})(function (require, exports) {
var mod_1 = require("./mod");
exports.t = mod_1.something + 1;
});
// System SimpleModule.js
System.register(["./mod"], function(exports_1) {
var mod_1;
var t;
return {
setters: [
function (mod_1_1) {
mod_1 = mod_1_1;
}],
execute: function() {
exports_1("t", t = mod_1.something + 1);
}
}
})
타입스크립트 컴파일 명령어를 칠 때 컴파일러 모드를 부여할 수 있다.
# commonjs 모드인 경우
tsc --module commonjs Test.ts
# amd 모드인 경우
tsc --module amd Test.ts
타입스크립트 선언 파일 d.ts
는 타입스크립트 코드의 타입 추론을 돕는 파일이다.
예를 들어, 전역 변수로 선언한 변수를 특정 파일에서 import
구문 없이 사용하는 경우 해당 변수를 인식하지 못한다.
그럴 때 아래와 같이 해당 변수를 선언해서 에러가 나지 않게 할 수 있다.
declare const global = 'sth';
해당 타입스크립트 파일에서 사용할 순 있지만 선언되어 있지 않은 전역 변수나 전역 함수는 아래와 같이 타입을 선언할 수 있다.
// 전역 변수
declare const pi = 3.14;
// 전역 함수
declare namespace myLib {
function greet(person: string): string;
let name: string;
}
myLib.greet('캡틴');
myLib.name = '타노스';
TIP
Use
declare namespace
to describe types or values accessed by dotted notation.
https://basarat.gitbook.io/typescript/main/common-errors
타입스크립트에서 배열 요소와 객체의 속성을 접근할 때는 인터페이스를 사용하면 된다.
자바스크립트에서는 아래와 같이 배열의 요소를 접근했다.
const arr = ['Thor', 'Hulk'];
arr[0]; // 'Thor'
타입스크립트에서는 인터페이스를 이용하여 아래와 같이 인덱싱 타입을 정의할 수 있다.
interface StringArray {
[index: number]: string;
}
const arr: StringArray = ['Thor', 'Hulk'];
arr[0]; // 'Thor'
배열의 인덱스의 타입은 숫자이고 이 인덱스로 특정 요소를 접근했을 때 해당 요소의 타입은 string
이라는 것을 명시했다.
배열에 강한 타입을 적용함과 동시에 배열의 요소롤 변경하지 못하게 하려면 아래와 같이 한다.
interface ReadonlyStringArray {
readonly [index: number]: string;
}
const arr: ReadonlyStringArray = ['Thor', 'Hulk'];
arr[2] = 'Capt'; // Error!
유틸리티 타입은 이미 정의해 놓은 타입을 변환할 때 사용하기 좋은 타입 문법이다.
유틸리티 타입은 꼭 쓰지 않더라도 기존의 인터페이스, 제네릭 등의 기본 문법으로 충분히 타입을 변환할 수 있지만 유틸리티 타입을 쓰면 훨씬 더 간결한 문법으로 타입을 정의할 수 있다.
자주 사용되는 타입을 몇 개 알아보겠다.
파셜(Partial) 타입은 특정 타입의 부분 집합을 만족하는 타입을 정의할 수 있다.
interface Address {
email: string;
address: string;
}
type MayHaveEmail = Partial<Address>;
const me: MayHaveEmail = {}; // 가능
const you: MayHaveEmail = { email: 'test@abc.com' }; // 가능
const all: MayHaveEmail = { email: 'capt@hero.com', address: 'Pangyo' }; // 가능
픽(Pick) 타입은 특정 타입에서 몇 개의 속성을 선택(pick)하여 타입을 정의할 수 있다.
interface Hero {
name: string;
skill: string;
}
const human: Pick<Hero, 'name'> = {
name: '스킬이 없는 사람',
}
type HasThen<T> = Pick<Promise<T>, 'then' | 'catch'>;
let hasThen: HasThen<number> = Promise.resolve(4);
hasThen.th // 위에서 'then'만 선택하면 'then'만 제공, 'catch' 선택하면 'catch만 제공'
오밋(Omit) 타입은 특정 타입에서 지정된 속성만 제거한 타입을 정의해 준다.
interface AddressBook {
name: string;
phone: number;
address: string;
company: string;
}
const phoneBook: Omit<AddressBook, 'address'> = {
name: '재택근무',
phone: 123422223333,
company: '내 방'
}
const chingtao: Omit<AddressBook, 'address'|'company'> = {
name: '중국집',
phone: 44455557777
}
맵드 타입이란 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법을 의미한다.
마치 자바스크립트 map()
API 함수를 타입에 적용한 것과 같은 효과를 가진다.
자바스크립트 map
AP는 배열을 다룰 때 유용한 자바스크립트 내장 API이다.
var arr = [{ id: 1, title: '함수' }, { id: 2, title: '변수' }, { id: 3, title: '인자' }];
var result = arr.map(function(item) {
return item.title;
});
console.log(result); // ['함수', '변수', '인자'];
위 코드는 3개의 객체를 요소로 가진 배열 arr
에 .map()
API를 적용한 코드이다.
배열의 각 요소를 순회하여 객체(id, title)에서 문자열로 변환하였다.
맵드 타입은 위에서 살펴본 자바스크립트 map
함수를 타입에 적용했다고 보면 된다.
이를 위해서는 아래와 같은 형태의 문법을 사용해야 한다.
{ [P in K]: T }
{ [P in K]?: T }
{ readonly [P in K]: T }
{ readonly [P in K]?: T }
맵드 타입의 가장 간단한 예제를 보겠다.
아래와 같이 헐크, 토르, 캡틴을 유니온 타입으로 묶어주는 Heroes
라는 타입이 있다고 하겠다.
type Heroes = 'Hulk' | 'Thor' | 'Capt';
여기서 이 세 영웅의 이름이 각각 나이까지 붙인 객체를 만들고 싶다고 한다면 아래와 같이 변환할 수 있다.
type HeroProfiles = { [K in Heroes]: number };
const heroInfo: HeroProfiles = {
Hulk: 54,
Thor: 1000,
Capt: 33
}
위 코드에서 [K in Heroes]
부분은 마치 자바스크립트의 for in
문법과 유사하게 동작한다.
앞으로 정의한 Heroes
타입의 3개의 문자열을 각각 순회하여 number
타입을 값으로 가지는 객체의 키로 정의가 된다.
예를 들면 아래와 같이 정의가 된다.
{ Hulk: number } // 첫번째 순회
{ Thor: number } // 두번째 순회
{ Capt: number } // 세번째 순회
따라서 위의 원리가 적용된 HeroProfiles
의 타입은 아래와 같이 정의된다.
type HeroProfiles = {
Hulk: number;
Thor: number;
Capt: number;
}
앞에서 살펴본 예제는 맵드 타입의 문법과 동작을 위해하기 위해 간단한 코드를 사용했다.
실제로 서비스를 개발할 때는 위와 같은 코드보다는 아래와 같은 코드를 더 많이 사용하게 된다.
type Subset<T> = {
[K in keyof T]?: T[K];
}
위 코드는 키와 값이 있는 객체를 정의하는 타입을 받아 그 객체의 부분 집합을 만족하는 타입으로 변환해 주는 문법이다.
예를 들면 아래와 같은 인터페이스가 있다고 할 때
interface Person {
age: number;
name: string;
}
위 Subset
타입을 적용하면 아래와 같은 객체를 모두 정의할 수 있다.
const ageOnly: Subset<Person> = { age: 23 };
const nameOnly: Subset<Person> = { name: 'Tony' };
const ironman: Subset<Person> = { age: 23, name: 'Tony' };
const empty: Subset<Person> = {};
아래와 같이 사용자 프로필을 조회하는 API 함수가 있다고 했을 때
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
function fetchUserProfile(): UserProfile {
// ...
}
이 프로필의 정보를 수정하는 API는 아마 아래와 같은 형태일 것이다.
interface UserProfileUpdate {
username?: string;
email?: string;
profilePhotoUrl?: string;
}
function updateUserProfile(params: UserProfileUpdate) {
// ...
}
이 때 아래와 같이 동일한 타입에 대해서 반복해서 선언하는 것을 피해야 한다.
interface UserProfile {
username: string;
email: string;
profilePhotoUrl: string;
}
interface UserProfileUpdate {
username?: string;
email?: string;
profilePhotoUrl?: string;
}
위의 인터페이스에서 반복되는 구조를 아래와 같은 방식으로 재활용 할 수 있다.
type UserProfileUpdate = {
username?: UserProfile['username'];
email?: UserProfile['email'];
profilePhotoUrl?: UserProfile['profileUPhotoUrl'];
}
혹은 좀 더 줄여서 아래와 같이 정의할 수도 있다.
type UserProfileUpdate = {
[p in 'username' | 'email' | 'profilePhotoUrl']?: UserProfile[p]
}
여기서 위 코드에 keyof
를 적용하면 아래와 같이 줄일 수 있다.
type UserProfileUpdate = {
[p in keyof UserProfile]?: UserProfile[p]
}
타입스크립트 설정 파일은 타입스크립트를 자바스크립트로 변환할 때의 설정을 정의해놓는 파일이다.
프로젝트에서 tsc
라는 명령어를 치면 타입스크립트 설정 파일에 정의된 내용을 기준으로 변환 작업(컴파일)을 진행한다.
tsc
명령어는 타입스크립트를 자바스크립트로 변환할 때 사용하는 명령어이다.
아래와 같이 입력하면 app.ts
파일이 app.js
로 변환된다.
tsc app.ts
tsc
명령어를 대상 파일을 지정하지 않고 실행하면 현재 폴더에 있는 타입스크립트 설정 파일을 기준으로 변환 작업을 수행한다.
만약 현재 폴더에 타입스크립트 설정 파일이 없다면 프로젝트 폴더 내에서 상위 폴더의 경로를 검색해 나간다.
tsconfig.json
파일의 경로를 명시적으로 지정하고 싶다면 아래 명령어를 사용하면 된다.
# 형식 예시
tsc --project 상대 경로
# 실제 명령어 예시
tsc --project ./src
tsc --p ./src
타입스크립트 변환 명령어를 입력할 때마다 대상 파일을 경로를 지정하지 않고 설정 파일을 미리 정의해 놓을 수 있다.
{
"files": ["app.ts", "./utils/math.ts"]
}
files
와 같이 파일을 개별로 지정하지 않고 include
옵션으로 변환할 폴더를 지정할 수 있다.
{
"include": ["src/**/*"]
}
TIP
와일드 카드 패턴
*
: 해당 디렉토리의 모든 파일 검색
?
: 해당 디렉토리 안에 파일의 이름 중 한글자라도 맞으면 해당
**
: 해당 디렉토리를 재귀적으로 접근(하위 디렉토리의 하위 티렉토리가 존재하는 경우 반복해서 접근)
WARNING
⚠ 위 와일드 카드 패턴에 해당하는 파일 확장자는
js
,jsx
,ts
,tsx
,d.ts
이다.
include
와 반대되는 속성으로 변환하지 않을 폴더 경로를 지정한다.
{
"exclude": ["node_modules"]
}
만약 설정하지 않으면 기본적으로 node_modules
, bower_componenets
같은 폴더를 제외한다.
TIP
컴파일 대상 경로를 정의하는 속성의 우선 순위 files > include = exclude
타입스크립트 설정 파일은 기본적으로 node_modules
를 제외하면 써드 파티 라이브러리의 타입을 정의해놓는 @types
폴더는 컴파일에 포함된다.
예를 들면 아래와 같다.
└─ node_modules
├─ @types => 컴파일에 포함
├─ lodash => 컴파일에서 제외
그런데 여기서 만약 @types
의 기본 경로를 변경하고 싶다면 아래와 같이 지정할 수 있다.
{
"compilerOptions": {
"typeRoots": ["./my-types"]
}
}
이렇게 되면 타이핑 라이브러리(써드 파티 라이브러리의 타입 정의 라이브러리)의 대상 경로가 node_modules/@types
에서 node_modules/my-types
로 변경된다.
특정 타입스크립트 설정 파일에서 다른 타입스크립트 설정의 내용을 가져와 추가할 수 있는 속성이다.
// config/base.json
{
"compilerOptions": {
"noImplicitAny":" true
}
}
// tsconfig.json
{
"extends": "./config/base"
}
확장난 파일의 내용을 가져다가 덮어쓰거나 새로 정의할 수도 있다.
타입스크립트 파일을 컴파일 했을 때 빌드 디렉토리에 생성되는 자바스크립트의 버전을 의미한다.
기본값인 es3
부터 es5
, es6
등 가장 최신 버전인 esnext
까지 있다.
// tsconfig.json
{
"target": "esnext"
}
타입스크립트 파일을 자바스크립트로 컴파일 할 때 포함될 라이브러리의 목록이다.
이 속성을 활용하는 가장 대표적인 사례는 async
코드를 컴파일 할 때 Promise
객체가 필요하므로 아래와 같은 설정을 해줘야 한다.
// tsconfig.json
{
"lib": ["es2015", "dom", "dom.iterable"]
}
여기서 es2015
는 프로미스 객체를 타입스크립트에서 인식할 수 있게 필요한 속성이고, dom
관련 속성은 DOM API를 사용하는 경우 필요하다.
타입스크립트 컴파일 작업을 진행할 때 자바스크립트 파일도 포함할 수 있는지를 설정해주는 속성이다.
주로 이미 기존에 존재하는 자바스크립트 프로젝트에 타입스크립트를 점진적으로 적용할 때 사용하면 좋은 속성이다.
자바스크립트로 만들어진 써드 파티 라이브러리(jQuery, lodash, chart 등)를 타입스크립트에서 사용하려면 각 기능에 대한 타입이 정의되어 있어야 한다.
예를 들면 아래와 같은 코드는 타입스크립트에서 제대로 동작하지 않는다.
// app.ts
import $ from 'jquery';
$(document).ready();
그 이유는 제이쿼리 라이브러리의 내부 코드에 대한 타입이 정의되어 있지 않아 이 라이브러리를 들고 와서 사용할 때 타입스크립트 파일에서 타입 추론을 할 수 없기 때문이다.
이런 경우에는 @types
라는 라이브러리를 설치하면 된다.
npm i -D @types/jquery
대중적으로 흔히 사용되는 자바스크립트 라이브러리는 대부분 @types
라는 별칭으로 타입스크립트 추론이 가능한 보조 라이브러리를 제공한다.
만약 이 라이브러리가 없는 경우에는 [스스로 선언하거나 다른 방법](아래 내용 작성 후 링크 제공)을 찾아봐야 한다.
types 라이브러리는 일반적으로 index.d.ts
파일과 package.json
파일로 구성되어 있다.
└─ @types/jquery
├─ index.d.ts
├─ package.json
package.json
파일 안에는 types
속성이 정의되어 있다.
Keep in mind that automatic inclusion is only important if you’re using files with global declarations (as opposed to files declared as modules).
TBD
자바스크립트 프로젝트에 타입스크립트를 적용하는 경우 아래의 절차를 따르면 도움이 된다.
TIP
TLDR
1. 타입스크립트 환경 설정 및 ts 파일로 변환
2.any
타입 선언
3.any
타입을 더 적절한 타입으로 변경
package.json
파일을 생성한다.npm i typescript -D
로 타입스크립트 라이브러리를 설치한다.tsconfig.json
을 생성하고 기본 값을 추가한다.{
"compilerOptions": {
"allowJs": true,
"target": "ES5",
"outDir": "./dist",
"moduleResolution": "Node",
"lib": ["ES2015", "DOM", "DOM.Iterable"]
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "dist"]
}
tsc
로 타입스크립트 파일을 자바스크립트 파일로 변환한다.js
파일을 모두 ts
파일로 변경한다.any
선언하기noImplicitAny: true
를 추가한다.@types
관련 라이브러리를 찾아 설치한다.any
를 선언한다.strict
모드 설정하기{
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
any
로 되어 있는 타입을 최대한 적절한 타입으로 변환한다.as
와 같은 키워드를 최대한 사용하지 않도록 고민해서 변경한다.