자바스크립트는 인터프리터 언어, 동적 타이핑 언어이다.
- 인터프리터 언어 : 소스코드를 한 줄 씩 읽어서 실행하는 언어.
- 컴파일 언어 : 소스코드를 기계어로 바꿔놓고 실행하는 언어.
- 동적 타이핑 언어 : 변수의 자료형을 자유롭게 바꿔서 사용 가능함.
- 정적 타이핑 언어 : 변수나 함수에 타입을 지정하고, 그 타입만을 사용하는 방식.
자바스크립트의 동적 타이핑 언어적 단점으로 실수하기 쉽고, 실수를 발견하기 어렵다는 점이 있다.
이러한 단점을 보완하기위해 정적 타이핑을 지원하는 타입스크립트가 있다.
타입스크립트는 정적 타이핑을 지원하기때문에 실제 배포하기 전에 타입을 체크한다.
다룰 내용
- TypeScript 프로젝트 생성
- 기본적인 타입
- enum, interface, 타입 별칭
- Union, Intersection, keyof, typeof 등 연산자
- generic
- tsconfig.json 설정
TypeScript 프로젝트 생성
TypeScript 프로젝트 생성 방법
1. 폴더 생성
2. 생성한 폴더를 nodejs프로젝트로 만든다.
npm init
3. 생성한 폴더를 TypeScript프로젝트로 만든다.
- 개발시에만 TypeScript을 사용할 것이기때문에 dev dependency로 설치한다.
npm install --save-dev typescript
4. 설정파일을 만든다.
npx tsc --init
- npx : node module을 실행하는 명령어
- tsc : 타입스크립트에서 제공하는 타입스크립트 컴파일러 모듈
- init : 초기 설정파일을 생성하는 옵션
5. package.json에 script를 추가한다.
"scripts": {
"build": "tsc"
},
TypeScript가 실행되는 과정
- TypeScript는 JavaScript에 문법을 추가해서 사용하는 언어이다.
- ( = TypeScript는 JavaScript의 super set 이다.)
- 웹 브라우저 또는 node.js는 타입스크립트 코드를 그대로 사용할 수 없으므로 자바스크립트로 변환된 코드를 실행해야한다.
- 이러한 변환을 해주는 것이 tsc = 타입스크립트 컴파일러이다.
- tsc는 타입스크립트 코드를 자바스크립트 코드로 트랜스파일해주는 프로그램이다.
- 아래와 같은 순서로 node.js / 웹 브라우저에서 코드가 실행될 수 있다.
- 타입검사
- 트랜스파일(ts -> js)
- node.js / 웹 브라우저에서 코드 실행
기본적인 타입
타입을 지정하는 방법
- 우리는 원래 아래와같이 타입이 없이 변수에 값을 할당했다.
- 타입스크립트에서는 아래와같이 타입을 명시하지 않으면, 타입을 추론해서 설정해준다.
- 아래 코드의 size변수는 타입스크립트에 의해 number타입으로 추론된다.
let size = 100;
위 변수의 타입을 명시하기위해서는 아래와 같이 작성한다.
let size: number = 100;
기본형 타입
let str: string = '문자열';
let num: number = 12; //( NaN, Infinity 를 포함)
let bool: boolean = true;
let undefindedData: undefined = undefined;
let nullData: null = null;
배열과 튜플
let arr1: string[] = ['a','b','c'];
let arr2: string[][] = [['a','b'],['c','d'];
let tuple: [number, number] = [3, 2];
객체
const monster:{
name: string,
level: number,
hasGold?: boolean,
skills: string[]
} = {
name: '고블린',
level: 22,
skills: ['태권도', '특공무술']
}
property에 type 설정
property의 개수와 상관없이 타입만 검사하도록 아래와같이 작성할 수 있다.
let stock: {
[id: string]: number;
} = {
c001: 3,
c002: 0,
c003: 1
}
* JavaScript에 비슷한 문법이 있었는데, 아래와 같다.
let filed = 'filed name';
let obj = {
[field]: 'field value'
}
any
- 타입오류로부터 자유롭게 만든다.
- TypeScript가 아닌, JavaScript와 같은 상태라고 볼 수 있다.
- 되도록 사용하지 않는 것이 좋다.
const parsedProduct = JSON.parse(
'{"name" : "토트백", "price": 12000}'
);
위와같이 JSON문자열의 경우, 어떤 객체타입이 될지 알 수 없으니 any타입이 된다.
하지만 이런 경우에도 any 타입으로 그대로 두기보다는 타입을 정해주는 것이 좋다.
const parsedProduct = {
name: string;
price: number;
} = JSON.parse(
'{"name" : "토트백", "price": 12000}'
);
위 코드는 아래와 같이 값에 타입을 지정해줄 수도 있다.
const parsedProduct = JSON.parse(
'{"name" : "토트백", "price": 12000}'
) as {
name: string;
price: number;
};
함수에 타입 지정
function addToCart(id: string, quantity: number = 1): boolean{
if(stock[id] < quantity){
return false;
}
return true;
}
function addManyToCart(...ids: string[]){
for(const id of ids){
addToCart(id);
}
}
const shopingmall: {
stock: { [id: string]: number};
cart: string[];
addToCart: (id: string, quantity?: number) => boolean;
addManyToCart: (...ids: string[]) => void;
} = {
stock: {
c001: 3,
c002: 1,
},
cart: [],
addToCart,
addManyToCart,
}
위 코드를 살펴보면,
파라미터들에 타입을 설정했고, 리턴타입도 설정했다.
addManyToCart 함수에서는 스프레드문법에 string[] 타입을 지정해준 것을 알 수 있다.
shopingmall 객체에도 타입이 지정되어있다.
enum, interface, 타입별칭
enum
let product: {
id: string;
name: string;
price: number;
sizes: string[];
} = {
id: 'c001',
name: '블랙 후디',
price: 129000,
sizes: ['M', 'L'],
}
- product변수의 sizes 프로퍼티는 사실 'S', 'M', 'L' 세가지뿐인데, string[] 타입을 갖기에는 범위가 넓다.
- 타입의 범위를 조금 줄일 방법 중 하나로 Enum을 사용할 수 있다.
- enum은 자바스크립트에는 없는 문법이다.
enum Size {
S = 'S',
M = 'M',
L = 'L',
}
let product: {
id: string;
name: string;
price: number;
sizes: Size[];
} = {
id: 'c001',
name: '블랙 후디',
price: 129000,
sizes: [Size.M, Size.L],
}
enum은 값을 지정해주지 않으면 기본값으로 0부터 1씩 증가하는 정수를 값으로 갖게된다.
기본값은 아래와같이 지정해준 것과 같다.
enum Size {
S = 0,
M = 1,
L = 2,
XL = 3,
}
이 경우, Size.S의 값이 0 으로, falsy값을 가지게 되므로 의도치 않은 버그를 발생시킬 수 있다.
따라서 기본값을 그대로 사용하기보다는 실제 값을 명시해주는 것이 좋다.
interface
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
let product1: Product = {
id: 'c001',
name: '블랙 후디',
price: 129000,
}
interface ClothingProduct extends Product {
sizes: Size[];
}
let product2: ClothingProduct = {
id: 'c001',
name: '블랙 후디',
price: 129000,
sizes: [Size.M, Size.L]
}
타입 별칭
리터럴 타입
let, const 에 따라 타입추론이 다르게 된다.
let str1 = 'aaa'; // string
const str2 = 'bbb'; // 'bbb'
- str2는 리터럴타입으로 추론된 것이다.
- 리터럴타입은 값을 곧 타입으로 하는 타입이다.
타입 별칭
- 타입에도 변수처럼 별칭을 붙여줄 수 있다.
- 복잡한 타입에 이름을 붙이고 재사용하고 싶을 때 사용한다.
- 타입별칭은 모든 타입에 붙일 수 있다.
type Cart = string[];
// Type
type Product = {
id: string;
name: string;
}
// Interface
interface Product {
id: string;
name: string;
}
- Type은 Interface와 같은 용도로 사용될 수 있는데, 가장 큰 차이점은 타입의 확장 가능/불가능 여부이다.
- 인터페이스는 합성, 상속과 같이 다양한 방법으로 확장이 가능한데 반해 타입별칭으로 선언된 타입은 확장이 불가능하다.
- 따라서 interface로 만드는 것을 권장한다.
Union, Intersection, keyof, typeof 등 연산자
Union타입
function printSizes(product: ClothingProduct | ShoeProduct) {
const availableSizes = product.sizes.join(',');
if('color' in product){
console.log(`색상: ${product.color}`);
}
}
- '|' 문자를 사용한다.
- 두 타입 모두 파라미터로 받을 수 있다.
- 리터럴타입과 사용하기 유용하다.
type ShoeSize = 220 | 225 | 230 | 235
Intersection타입
- 타입을 합치는 것으로 interface의 상속과 같다.
- 하지만 interface의 사용을 권장한다.
interface Id{
id: string;
}
interface Timestamp {
createdAt: Date;
updatedAt: Date;
}
type Product = Id & {
name: string;
prie: number;
}
type User = Id & Timestamp & {
username: string;
}
'&' 문자를 사용한다.
keyof연산자
interface Product {
id: string;
name: string;
price: number;
}
type ProductProperty = keyof Product;
const productTablekeys: ProductProperty[] = ['name', 'price'];
keyof 연산자를 사용하면, Produce interface의 구조가 변경되어도 그대로 반영시킨 type을 얻을 수 있다.
아래와같이 사용하면 타입별칭을 만들지 않고 바로 적용시킬수도 있다.
interface Product {
id: string;
name: string;
price: number;
}
const productTablekeys: (keyof Product)[] = ['name, 'price'];
typeof연산자
- 자바스크립트에서의 typeof는 값의 타입을 문자열로 리턴해주는 함수였다.
- 타입스크립트에서의 typeof는 값의 타입을 리턴해준다.
let product2: typeof product;
위 코드는 product가 Product타입이므로, product2도 Product타입으로 추론되는것을 확인할 수 있다.
enum과 타입별칭
enum을 사용하는 경우(권장)
enum UserType {
Admin = 'admin',
User = 'user',
Guest = 'guest',
}
const role = UserType.Admin;
console.log(role === UserType.Guest);
console.log(Object.keys(UserType)); // ['Admin', 'User', 'Guest']
타입별칭과 Union을 사용하는 경우
type UserType = 'admin' | 'user' | 'guest'
const role: UserType = 'admin';
console.log(role === 'guest');
interface와 타입별칭
interface를 사용하는 경우(권장)
interface Entity {
id: string;
createdAt: Date;
updatedAt: Date;
}
interface User extends Entity {
username: string;
email: string;
}
타입별칭을 사용하는 경우
type Entity = {
id: string;
createdAt: Date;
updatedAt: Date;
}
type User = Entity & {
username: string;
email: string;
}
generic
function printArray<T>(items: T[]) {
for(const item of items) {
console.log(item);
}
}
- <T> : 이 부분을 타입 파라미터라고 부른다.
- 보통 T, U, V 로 임의의 타입을 표현한다.
- 아래와같이 사용가능하다.
const shoeSizes: number[] = [95, 100, 105];
const clothingSizes: string[] = ['S','M','L'];
printArray(shoeSizes);
printArray(clothingSizes);
number인 shoeSizes와 , string 배열인 clothingSizes 모두 printArray함수의 파라미터로 넣을 수 있다.
함수를 호출할 때에 타입파라미터를 설정할수도있다.
printArray<number>(shoeSizes);
위와같이 작성하면, 파라미터로 number배열이 아닌 타입을 넣으면 에러가 발생한다.
interface에서의 사용
interface SizeProduct<T> extends Product {
sizes: T[];
}
enum ClothingSize {
S='S',
M='M',
L='L'
}
type ClothingProduct = SizeProduct<ClothingSize>;
타입별칭에서의 사용
type Pair<T> = [T, T];
const point: Pair<number> = [1,2];
const fullname: Pair<string> = ['은','행운'];
- 타입파라미터의 이름은 보통 T, U, V를 많이 사용하지만 다른 이름도 사용할 수 있다.
- 다른 이름을 사용할경우에는 타입파라미터인지, 아니면 실제로 존재하는 어떤 타입인지 알기 어려우니, 이름 앞에 T를 붙여주자.
- 예를들면 아래와같이.
type Pair<TItem> = [TItem, TItem];
JavaScript에서의 사용
querySelector() 함수
- 기본적으로 querySelector는 어떤 DOM노드가 리턴될지 모르기때문에 HTMLElement타입으로 정의되지만, 타입이 확실한경우는 아래와같이 작성할 수 있다.
const elem = document.querySelector<HTMLInputElement>('input.username');
Map
- 키와 밸류를 갖는 자료구조.
const productMap = new Map<string, Product>();
productMap.set(product1.id, product1);
productMap.set(product2.id, product2);
Set
const productSet = new Set<Product>();
productSet.add(product1);
productSet.add(product2);
유용한 타입들
1. 키와 밸류 정하기 : Record
객체에 키와 밸류타입을 정해놓고싶을 때 사용한다.
Map과 비슷하지만 차이점은 순수한 객체에 타입만 추가한다는 점이다.
const productMap: Record<string, Product> = {}
productMap['c001'] = product1;
productMap['c002'] = product2;
2. 객체 프로퍼티 고르기 : Pick
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
type ProductInfo = Pick<Product, 'name' | 'price'>;
// 아래와 같다.
type ProductInfo = {
name: string;
price: number;
}
3. 객체의 프로퍼티 생략하기 : Omit
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
type ProductInfo = Omit<Product, 'id' | 'membersOnly'>;
// 아래와 같다.
type ProductInfo = {
name: string;
price: number;
}
4. Union 제거하기 : Exclude
아래 코드는 Coupon에서 EmployeeCoupon만 제거하고 싶을 때 Exclude를 사용한 예이다.
type Coupon =
| PromotionCoupon
| EmployeeCoupon
| WelcomCoupon
| RewardCoupon
;
type InternalCoupon = EmployeeCoupon;
type PublicCoupon = Exclude<Coupon, InternalCoupon>;
// type PublicCoupon = PromotionCoupon | WelcomCoupon | RewardCoupon
5. 함수 파라미터 타입 가져오기 : Parameters
function addToCart(id: string, quantity: number = 1): boolean {
// ...
return true;
}
type AddToCartParameters = Parameters<typeof addToCart>;
// type AddToCartParameters = [id: string, quantity: number | undefined]
6. 함수의 리턴 타입 가져오기 : ReturnType
function addToCart(id: string, quantity: number = 1): boolean {
// ...
return true;
}
type AddToCartResult = ReturnType<typeof addToCart>;
// type AddToCartResult = boolean
tsconfig.json 설정
compilerOptions
"target" : "es2016"
"module": "commonjs",
"esModuleInterop" : true,
"forceConsistentCasingInFileNames" : true,
"strict" : true;
"noImplicitAny": true,
"strictNullChecks" : true,
"skipLibCheck" : true,
"outDir" : "./dist",
"rootDir" : "src"
"resolveJsonModule": true,
"target" : "es2016" // 어떤 ECMAScript버전으로 변환할지
"module": "commonjs", // 어떤 방식으로 모듈을 만들지
"esModuleInterop" : true, // ES모듈을 안전하게 사용
"forceConsistentCasingInFileNames" : true, // 파일의 대소문자 구분하기
"strict" : true; // 엄격한 규칙들 켜기
"noImplicitAny": true, // 타입정의가 없고, 타입추론이 되지 않는 상태에 대한 설정
"strictNullChecks" : true, // null 가능성을 처리하는 옵션
"skipLibCheck" : true, // 설치한 패키지의 타입검사 하지 않기
"outDir" : "./dist", // 자바스크립트 파일을 생성할 경로
"rootDir" : "src" // 최상위 폴더
"resolveJsonModule": true, // .json 파일을 임포트해야하는 경우 켜기
include/exclude 옵션
"compilerOptions" : {
...
},
"include" : ["src/**/*"], // src폴더 아래의 모든 폴더, 모든 파일을 의미한다.
"exclude" : ["src/test.ts"]
- include옵션으로 빌드시킬 경로를 지정하고,
- exclude옵션으로 빌드에서 제외시킬 경로를 설정한다.
tsconfig.json파일 불러오기
1. 권장하는 tsconfig.json 설정을 모아놓은 tsconfig/bases리포지토리에 있는 설정파일을 패키지로 설치한 후, 불러와서 사용해보자.
npm install --save-dev @tsconfig/recommended
2. 그리고 tsconfig.json파일에 extends속성을 추가한다.
{
"extends": "@tsconfig/recommended/tsconfig.json",
"compilerOptions": {
}
}
3. 불러온 옵션에 target 속성의 값이 "es2015"로 되어있는데, 나는 es2016으로 빌드하고싶으니, 덮어쓴다.
{
"extends": "@tsconfig/recommended/tsconfig.json",
"compilerOptions": {
"target": "ES2016"
}
}
'TypeScript' 카테고리의 다른 글
TypeScript 사용 이유와 동작 원리 (0) | 2023.11.26 |
---|---|
TypeScript-React (0) | 2023.11.22 |
typeScript 프로젝트 생성 (0) | 2022.09.15 |
typeScript 기본 지식 (0) | 2022.09.15 |