[코드잇] TypeScript 기본기
◆ TypeScript 시작하기
◆ Enum
◆ Interface
◆ 그 밖의 타입들
◆ 제네릭
◆ tsconfig.json
TypeScript 시작하기
TypeScript 프로젝트 만들기
Node.js 프로젝트 생성
npm init
타입스크립트 개발모드로 설치
npm install --save-dev typescript
타입스크립트 사용에 필요한 설정 파일 설치
npx tsc --nit
· npx : node 모듈을 시행하는 명령어
· tsc : 타입스크립트에서 제공하는 타입스크립트 컴파일러 모듈 (TS를 JS로 바꿔줌)
· --init : 초기 파일 생성
package.json 파일에서 스크립트 추가
"scripts": {
"build": "tsc"
},
main.ts 파일 생성
console.log('안녕 타입스크립트!');
터미널에서 타입스크립트 컴파일러 실행
npm run build
main.js
"use strict";
console.log('안녕 타입스크립트!');
단축 명령어 설정
"scripts": {
"build": "tsc",
"start": "node main.js"
},
TypeScript가 실행되는 과정
타입스크립트는 자바스크립트의 상위집합(superset)
컴파일(compile)
- 한 프로그래밍 언어에서 다른 프로그래밍 언어로 번역하는 것
- 소스코드를 컴퓨터가 실행할 수 있는 코드나 기계어로 바꾸는 것을 의미함.
- 웹개발에서는 특별히 Transpile(트랜스파일)이라고 함.
타입스크립트 컴파일러(TSC)
- 타입스크립트 코드를 자바스크립트 코드로 트랜스파일 해주는 프로그램
- 타입스크립트 컴파일러는 트랜스파일 하기 전에 타입 검사(Type Check)를 함.
- TSconfig.js파일에서 컴파일 버전을 설정할 수 있음.
Deno : 타입스크립트 사용, 자체적으로 타입검사, 트랜스파일링을 해주어서 TS쓰기 위해 프로젝트 세팅을 할 필요가 없음.
타입을 정하는 법
정적 타이핑 언어 : 변수에 타입이 정해지면 반드시 타입을 지켜야 함.
let size = 100;
size = "L";
타입을 추측할 수 있는 경우에는 따로 타입을 적어주지 않아도 타입이 추론됨.
size 변수가 숫자형으로 정해졌기 때문에 문자열을 할당하면 타입오류가 남.
VSCode에서 보여주는 타입오류는 스크립트 컴파일러가 주는 것과 동일하다.
직접 타입을 정하기
TypeScript
let size: number = 100;
size = 105;
JavaScript
"use strict";
let size = 100;
size = 105;
타입은 타입 검사에만 쓰이고, 실제 코드 실행은 영향을 주지 않음.
타입 오류 이해하기
let product = {
id: 'c001',
name: '라이트 윈드 브레이커',
price: 129000,
sizes: ['M', 'L', 'XL'],
};
// ...
const newProduct = {
id: 'c002',
name: '다크 윈드 브레이커',
price: 139000,
sizes: [90, 95, 100, 105, 100],
};
// ...
product = newProduct;
product와 newProduct의 타입이 맞지 않다는 뜻임.
기본형
let itemName: string = '코드잇 블랙 후드';
let itemPrice: number = 129000;
let membersOnly: boolean = true;
let owner: undefined = undefined;
let seller: null = null;
let num = 2/0; // Infinity
let num2 = 0/0; // NaN
// Number.is 로 Number인지 검사
배열과 튜플
- 배열 : 타입[] (ex. string[])
- 튜플 : [타입, 타입] (개수가 정해져 있음) (ex. [number, string])
배열도 자료형 지정 가능
// 문자형 배열
const cart: string[] = [];
cart.push('c001');
cart.push('c002');
cart.push(3);
// 2차원 배열
const carts: string[][] = [
['c001', 'c002'],
['c003'],
];
// 배열의 크기는 정해져 있지 않음
let mySize: number[] = [167, 28];
mySize = [167, 28, 255];
mySize = [255];
mySize = [];
배열이지만 개수를 명확하게 쓰고 싶으면 튜플을 쓰면 됨.
튜플은 배열에 값을 정하듯이 쓰면 됨. 요소자리에 타입 지정하기
// 튜플
let mySize: [number, number, string] = [167, 28, 'M'];
// 개수가 다름
mySize = [167, 28];
mySize = [255];
mySize = [];
// 자료형이 다름
mySize = [167, '28inch', 34]
객체 타입
프로퍼티의 개수를 알 수 없거나, 개수를 정해놓고 싶지 않은 경우에 프로퍼티 값의 타입만 지정할 수 있음.
옵셔널 프로퍼티 : 객체 내 있어도 되고 없어도 되는 속성, '?'로 표시
let product: {
id: string;
name: string;
price: number;
membersOnly?: boolean;
sizes: string[];
} = {
id: 'c001',
name: '코드잇 블랙 후디',
price: 129000,
sizes: ['M', 'L', 'XL'],
};
if (product.membersOnly) {
console.log('회원 전용 상품');
} else {
console.log('일반 상품');
}
인덱스 시그니처(Index Signature)
{ [키타입: 키타입명]: 값타입 }
let field = 'field name';
let obj = {
[field]: 'field value',
};
// 상품의 재고 숫자만 만드는 객체
let stock: {
[productId: string]: number;
} = {
c001: 3,
c002: 0,
c003: 1,
c004: 'codeit', // 타입 에러
}
any
객체 product를 any로 지정하면 타입오류가 발생하지 않음.
되도록 사용하지 않는 것이 좋음.
자동으로 타입을 알 수 없는 경우 불가피하게 any가 됨. → as 나 콜론을 쓰기
const product: any = {
id: 'c001',
name: '코드잇 블랙 후디',
price: 129000,
sizes: ['M', 'L', 'XL'],
};
console.log(product.reviews[2]);
JSON.parse 함수에서는 어떤 문자열이 어떤 객체타입을 될지 알 수 없어서 결과값이 any 타입임.
이때 any타입을 그대로 쓰기보다는 타입을 정의해주는 것이 좋음.
const parsedProduct : {
name: string;
price: number;
} = JSON.parse(
'{ "name": "코드잇 토트백", "price": 12000 }'
);
as 키워드 활용
const parsedProduct = JSON.parse(
'{ "name": "코드잇 토트백", "price": 12000 }'
) as {
name: string;
price: number;
};
문법적인 문제로 꺽쇄는 잘 쓰지 않음.
const parsedProduct = <{
name: string;
price: number;
}> JSON.parse(
'{ "name": "코드잇 토트백", "price": 12000 }'
);
함수에 타입 정의하기
함수 타입을 선언하지 않으면 암묵적으로 Any를 선언하는 것이라 타입오류가 남.
"strict": true,
"noImplicitAny": true
→ 암묵적으로 Any를 선언하는 것을 금지함.
파라미터 옆에 콜론과 자료형을 적어준다.
리턴값이 추론되는 경우(return false) 굳이 자료형 안 적어줘도 됨.
const stock: { [id: string]: number } = {
c001: 3,
c002: 1,
};
const cart: string[] = [];
function addToCart(id: string, quantity: number): boolean {
if (stock[id] < quantity) {
return false;
}
stock[id] -= quantity;
for (let i = 0; i < quantity; i++) {
cart.push(id);
}
return true;
}
함수에 기본값 정하기
quantity 파라미터를 쓸 수도 있고, 안 쓸 수도 있다고 하자.
파라미터를 전달하지 않으면 1로 처리
function addToCart(id: string, quantity?: number): boolean {
if (typeof quantity === 'undefined') {
quantity = 1;
}
function addToCart(id: string, quantity: number = 1): boolean {
함수 자체의 타입을 정하기
const codeitmall: {
stock: { [id: string]: number };
cart: string[];
addToCart: (id: string, quantity?: number) => boolean;
addManyToCart: (...ids: string[]) => void;
} = {
stock: {
c001: 3,
c002: 1,
},
cart: [],
addToCart,
addManyToCart,
};
function addToCart(id: string, quantity?: number) {
if (!quantity) {
quantity = 1;
}
if (codeitmall.stock[id] < quantity) {
return false;
}
codeitmall.stock[id] -= quantity;
for (let i = 0; i < quantity; i++) {
codeitmall.cart.push(id);
}
return true;
}
// rest 파라미터 : 배열로 지정
// 아무것도 리턴하지 않으면 void로 적기
function addManyToCart(...ids: string[]) {
for (const id of ids) {
addToCart(id);
}
}
console.log(codeitmall);
함수를 값으로 사용하는 경우 화살표 함수처럼 작성
(id: string, quanity: number) => boolean
Rest 파라미터는 배열 타입으로 작성
함수가 값을 리턴하지 않는 경우 리턴 타입을 void로 설정
(...ids: string[]) => void;
Enum
사이즈가 S, M, L 밖에 없는데 자료형이 string으로 지정되어 있어서 광범위한 느낌이 있음.
enum의 기본값은 0부터 시작하므로 주의해야함.
enum Size {
S,
M,
L,
XL,
}
function findProduct(size?: Size) {
if (!size) {
console.log('전체 사이즈로 검색');
return;
}
console.log('특정 사이즈로 검색');
}
findProduct(Size.M); // 특정 사이즈로 검색
findProduct(Size.S); // 전체 사이즈로 검색
findProduct(); // 전체 사이즈로 검색
Size.S가 0이므로 falsy값으로 처리되어 값이 없는 경우와 똑같이 처리된 것을 확인할 수 있음.
enum을 시작할 때는 값을 정해놓고 쓰는 것이 좋음.
enum Size {
S = 'S',
M = 'M',
L = 'L',
XL = 'XL',
}
function findProduct(size?: Size) {
if (!size) {
console.log('전체 사이즈로 검색');
return;
}
console.log('특정 사이즈로 검색');
}
findProduct(Size.M); // 특정 사이즈로 검색
findProduct(Size.S); // 특정 사이즈로 검색
findProduct(); // 전체 사이즈로 검색
Interface
객체타입을 재사용하기 좋도록 하는 문법
interface 대문자
interface 하위인터페이스 extends 상위인터페이스 (프로퍼티가 상속됨)
enum Size {
S = 'S',
M = 'M',
L = 'L',
XL = 'XL',
}
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
// 하위(상속받음) extends 상위1, 상위2
interface ClothingProduct extends Product {
sizes: Size[];
}
const product1: ClothingProduct = {
id: 'c001',
name: '코드잇 블랙 후드 집업',
price: 129000,
sizes: [Size.S, Size.M],
membersOnly: true,
};
const product2: ClothingProduct = {
id: 'd001',
name: '코드잇 텀블러',
price: 25000,
sizes: [Size.M]
};
// 인터페이스로 함수 타입 지정
interface PrintProductFunction {
(product: Product): void;
}
const printProduct: PrintProductFunction = (product) => {
console.log(`${product.name}의 가격은 ${product.price}원입니다.`)
}
printProduct(product1);
printProduct(product2);
그밖의 타입들
리터럴 타입 : 값을 곧 타입으로 하는 타입
let productName1 = '코드잇 블랙 후드'; // string
const productName2 = '코드잇 텀블러'; // '코드잇 텀블러' 문자열 자체
let small = 95; // number
const large = 100; // 100 자체
문자 리터럴 타입은 문자형 타입에 포함됨.
숫자 리터럴 타입은 숫자형 타입에 포함됨.
function printSize(size: number) {
console.log(`${size} 사이즈입니다.`);
}
printSize(small); // 95 사이즈입니다.
printSize(large); // 100 사이즈입니다.
size: number로 숫자형 타입을 지정해주면 100이 할당된 large도 제대로 출력됨.
특정 자료형의 값만 쓰면 오류가 남.
function printSize(size: 100) {
console.log(`${size} 사이즈입니다.`);
}
printSize(small); // 에러
printSize(large);
타입 별칭
type alias (타입 별칭) : 타입도 변수처럼 이름을 붙여줄 수 있음.
const cart: string[] = [
'c001',
'c001',
'c002',
];
interface User {
username: string;
email: string;
cart: string[];
}
const user: User = {
username: 'codeit',
email: 'typescript@codeit.kr',
cart,
}
이 코드에서 cart: string[]이 불필요하게 반복됨.
cart: string[]
type Cart를 선언한 다음 string[]에는 다 Cart로 대체해줌.
type Cart = string[];
type CartResultCallback = (result: boolean) => void;
interface Product {
id: string;
name: string;
}
const cart: Cart = [
'c001',
'c001',
'c002',
];
interface User {
username: string;
email: string;
cart: Cart;
}
const user: User = {
username: 'codeit',
email: 'typescript@codeit.kr',
cart,
}
Union 타입
- Union : A | B, A타입이거나 B타입이다.
- Intersection : A & B, A타입이랑 B타입을 합친 것이다.
Union일 때는 ClothingProduct와 ShoeProduct일 떄를 모두 고려하기 때문에 공통된 프로퍼티만 참조할 수 있음.
특정 타입에 대해서 뭘하고 싶으면 if in을 쓰면 됨.
interface ClothingProduct extends Product {
sizes: ClothingSize[];
color: string;
}
interface ShoeProduct extends Product {
sizes: ShoeSize[];
handmade: boolean;
}
// ClothingProuduct와 ShoeProduct 둘 중 하나만 받고 싶은 경우
function printSizes(product: ClothingProduct | ShoeProduct) {
const availableSizes = product.sizes.join(', ');
console.log(`구매 가능한 사이즈는 다음과 같습니다: ${availableSizes}`);
// if문에서 특정 타입만 처리하도록
if ('color' in product) {
console.log(`색상: ${product.color}`);
}
if ('handmade' in product) {
console.log(
product.handmade
? '이 상품은 장인이 직접 만듭니다.'
: '이 상품은 공장에서 만들어졌습니다.'
);
}
}
Intersection 타입
여러 객체 타입을 합칠 때 사용, '&'로 표시함
interface Id {
id: string;
}
interface Timestamp {
createdAt: Date;
updatedAt: Date;
}
type Product = Id & {
name: string;
price: number;
membersOnly?: boolean;
}
type User = Id & Timestamp & {
username: string;
email: string;
}
type Review = Id & Timestamp & {
productId: string;
userId: string;
content: string;
}
interface 상속
interface Entity {
id: string;
}
interface TimestampEntity extends Entity {
createdAt: Date;
updatedAt: Date;
}
interface Product extends Entity {
name: string;
price: number;
membersOnly?: boolean;
}
interface User extends TimestampEntity {
username: string;
email: string;
}
interface Review extends TimestampEntity {
productId: string;
userId: string;
content: string;
}
타입스크립트에서 타입은 Structural Subtyping(Structural Type System)
구조가 같은 타입이면 같은 타입이라고 판단함.
keyof와 typeof 연산자
keyof 연산자
객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성
interface Product {
id: string;
name: string;
price: number;
salePrice: number;
membersOnly?: boolean;
}
type ProductProperty = 'id' | 'name' | 'price' | 'salePrice' | 'membersOnly';
const productTableKeys: ProductProperty[] = ['name', 'price', 'membersOnly', 'salePrice'];
interface Product {
id: string;
name: string;
price: number;
salePrice: number;
membersOnly?: boolean;
}
type ProductProperty = keyof Product;
const productTableKeys: (keyof Product)[] = ['name', 'price', 'membersOnly', 'salePrice'];
typeof 연산자
존재하는 타입을 가져와서 타입을 정리할 때 사용
컴파일 타임에 변수, 객체 또는 함수의 유형을 출력함.
// TS : 결과가 타입스크립트의 타입으로 나옴.
// 존재하는 타입을 가져와서 타입을 정리할 때 사용
let product2: typeof product;
// JS : 어떤 값의 타입을 문자열로 출력해주는 연산자
console.log(typeof product); // object
const person = {
name: "John",
age: 30,
};
type PersonType = typeof person;
// PersonType is { name: string, age: number }
- Enum vs 타입 별칭
enum UserType {
Admin = 'admin',
User = 'user',
Guest = 'guest',
}
const role = UserType.Admin;
console.log(role === UserType.Guest);
type UserType = 'admin' | 'user' | 'guest'
const role: UserType = 'admin';
console.log(role === 'guest');
Enum은 별도의 자바스크립트 객체를 만들어서 사용하고, 타입 별칭은 단순히 값만 사용한다.
- Interface vs 타입 별칭
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;
}
Interface의 목적에 맞으면 되도록 Interface를 사용하자.
제네릭
타입을 마치 함수의 파라미터처럼 사용하는 것
제네릭에서는 보통 T, U, V로 임의의 타입을 표현함.
// T는 타입 파라미터
function printArray<T>(items: T[]) {
for (const item of items) {
console.log(item);
}
}
printArray<boolean>(shoeSizes);
printArray(clothingSizes);
interface SizeProduct<T> extends Product {
sizes: T[];
}
type Pair<T> = [T, T];
// 길게 쓰고 싶으면 이름 앞에 'T' 붙이기
// type Pair<TItem> = [TItem, TItem];
const point: Pair<number> = [1, 2];
const fullname: Pair<string> = ['김', '코드잇'];
const map = new Map<string, Product>();
tsconfig.json
모듈 만들기
· import, export 문법은 자바스크립트와 동일하다.
· export default는 enum 앞에서는 불가능하다.
· 기본적으로 CommonJS 모듈 문법으로 바뀌어져서 나오는데, 설정을 통해 변경 가능함.
outDir
빌드된 파일은 따로 폴더에 모이면 좋겠음.
→ tsconfig.json 파일에 설정
빌드한 파일을 모아놓을 경로를 정할 수 있음.
아무것도 지정하지 않는 경우("./") : 소스파일과 트랜스파일링된 파일이 같은 경로에 있음.
"outDir": "./dist",
dist라는 폴더 안에 JS 파일이 모여 있음.
include/exclude
타입스크립트 처리할 때 어떤 파일을 포함/제외할 것인지 설정
배열 안에는 경로패턴을 문자열로 적어주면 됨.
{
"compilerOptions": {
},
"include" : ["src/**/*"], // src 폴더 안에 있는 모든 경로의 파일을 포함
"exclude" : ["src/test2.ts"]
}
rootDir
컴파일 하는 파일들의 공통된 조상 파일을 찾아서 그 경로를 기본값으로 함.
"rootDir": "./",
tsconfig.json 파일이 있는 폴더를 최상위 폴더로 해서 폴더 구조를 그대로 옮겨 놓음.
target | 어떤 ECMAScript 버전으로 변환할 것인지 기본적으로 ES7(ES2016)으로 변환하도록 되어 있음. |
module | 어떤 방식으로 모듈을 만들지 ESM - es6, es2020 / CJS - commonjs |
esModuleInterop | ES 모듈을 안전하게 사용 안전하게 사용하려면 true로 해놓는 것을 권장함. |
forceConsistentCasingInFileNames | 파일의 대소문자 구분하기 ex. Main.ts, main.ts |
strict | 엄격한 규칙들 켜기 · noImplicitAny : implicit any = 기존 자바스크립트 코드처럼 타입 없이 사용 · strictNullChecks : null이 될 가능성이 있다면 반드시 처리하는 옵션 |
stkipLibCheck | node_modules 폴더에 설치한 패키지의 타입 검사하지 않기 |
rootDir | 최상위 폴더 |
outDir | 자바스크립트 파일을 생성할 폴더 (값을 지정하지 않으면 소스코드 파일과 같은 폴더에 만듦) |
resolveJsonModule | JSON 파일 임포트하기 |
include / exclude | **/* : 아래의 모든 폴더, 모든 파일 (Glob 패턴) |
https://www.typescriptlang.org/tsconfig
TSConfig Reference - Docs on every TSConfig option
From allowJs to useDefineForClassFields the TSConfig reference includes information about all of the active compiler flags setting up a TypeScript project.
www.typescriptlang.org