npm init 입력해서 node js 프로젝트 설치 후
npm install --save-dev typescript
잘 설치되었다면
pakage.json 파일의 devDependencies 에
typescript 추가됨.
npx tsc --init
tsconfig.json 생성됨
pakage.json > scripts 중괄호 안에
"build": "tsc"
추가하면 됨.
.ts 확장자로 파일 생성 후
npm run build 로 컴파일 가능.
슈퍼 셋)변수 이름 뒤에 : 을 사용 후 타입을 명시하면 됨.
let size : number = 100;
![[Pasted image 20240827042431.png]]
위에서부터 밑에 까지 큰 범위에서의 오류 > 작은 범위의 오류까지 친절히 알려줌.
에러가 있는 상태에서 npm run build 해서 타입 검사 하면
에러 줄 위치도 알 수 있고, ctrl + 클릭 하면 해당 오류의 코드로 바로 이동
string : 문자형
number : 숫자형
boolean : 불린형
undefined : 언디파인드
null : null;
undefined와 null 은 서로 다른 타입이다.
타입으로 특정타입의 배열 을 만들고 싶으면 아래처럼
const cart : string[] = [];
2차원 배열을 만드려면
const carts : string[][] = [
['one', 'two'],
['hi'],
];
배열은 개수가 정해져 있지 않아서 아래 코드는 오류 없이 작동함.
let mySize : number[] = [167,28];
mySize = [167, 28, 255];
mySize = [255];
mySize = [];
배열의 개수와 타입을 명확하게 정하고 싶으면
튜플 을 쓰면 됨.
let mySize : [number, number, string] = [123, 23, 'M'];
객체 타입 정의는 객체를 정의할 때랑 비슷하다. 다만 마지막에 쉼표 대신 세미콜론을 쓴다는 점이 다르다.
let stock : {
id: string;
name: string;
price: number;
membersOnly: boolean;
sizes: string[];
} = {
....
}
객체 타입을 정의하면
프로퍼티 이름을 외우고 있지 않아도 vs-code에서 프로퍼티 이름을 추천해줌.
인텔리전스 서제스트 단축키 : ctrl + i
선택적인 프로퍼티를 옵셔널 프로퍼티 라고 부름.
옵셔널 프로퍼티 는 값을 할당하지 않아도 문제가 없음.
let stock : {
id: string;
name: string;
price: number;
membersOnly?: boolean; // 이렇게 프로퍼티 뒷쪽에 ? 기호를 넣으면 됨.
sizes: string[];
} = {
....
}
프로퍼티의 개수를 알 수 없거나 ,정해놓고 싶지 않은 경우에
프로터티 값에 타입만 지정할 수 있다.
let stock : {
[id: string] : number;
} = {
c001: 3;
c002: 2;
c003: 2;
c004: 2;
.
}
any타입은 모든 타입을 허용할 수 있는 타입.
즉, 타입스크립트 타입 검사를 무력화 시킴.
const product = {
id: "c001",
name: "코드잇 블랙 후디",
price: 129000,
sizes: ["M", "L", "XL"],
};
console.log(product.reviews[2]); // 타입 명시가 없어서 에러.
const product : any = {
id: "c001",
name: "코드잇 블랙 후디",
price: 129000,
sizes: ["M", "L", "XL"],
};
console.log(product.reviews[2]); // any 사용으로 타입 사용 자유로워짐
any 는 어쩔 수 없는 경우에만 사용해야함.
타입 검사를 빡세게 해서 오류를 줄이려는건데, any를 사용하면 무용지물.
JSON.parse 같은 경우에는
값의 타입을 미리 알 수 없는 경우가 있음.
'as' 키워드란 컴파일 단계에서 타입 검사를 할 때
타입스크립트가 감지하지 못하는 애매한 타입 요소들을 직접 명시해주는 키워드.
한 가지 특징은 as 키워드(type assertion)는 오직 컴파일 단계에서만 실행되며 런타임 단계에서는 삭제된 채로 실행된다. 컴파일 단계에서 제대로 넘어갔다면 런타임 단계에서의 에러 발생은 일어나지 않는다는 말.
함수 선언에서 타입을 정하기
function addToCart(id: string, quantity: number = 1) : string {
...
}
함수 자체에 타입을 정하기
const codeitmall: {
addToCart: (id: string, quantity?: number) => boolean;
} = {
addToCart,
}
리턴값이 없을 때는 void 타입을 명시해주면 됌.
const codeitmall: {
addToCart: (id: string, quantity?: number) => void;
} = {
addToCart,
}
열거형 타입을 선언할 때 Enum을 사용함.
열거형 타입이란 특정 상수의 집합.
값의 종류를 나열할 땐 Enum을 사용함.
enum Size {
S,
M,
L,
XL,
}
Enum을 타입으로 쓸 때는 Enum 이름만 적으면 되고
값으로 쓸 때는 객체처럼 . 표기법으로 사용하면 된다.
// enum을 타입으로 사용하기
let product : Size = Size.S;
Enum 에 값을 선언하지 않으면
순서대로 숫자형 값이 들어감.
이때 기본값은 0부터 시작하는 정수라는 점에 주의
enum Size { S, M, L, XL, }
console.log(Size.S); // 0
console.log(Size.M); // 1
console.log(Size.L); // 2
숫자 0은 실수하기 쉽기 때문에
Enum을 사용할 땐 되도록이면 값을 정해놓고 쓰는 게 좋음
밑에 코드처럼 임의로 값을 지정할 수 있음.
enum Job {
Knight = `Knight`,
Archer = `Archer`,
Mage = `Mage`,
Priest = `Priest`,
Thief = `Thief`,
}
인터페이스 는 객체의 구조를 정의하는데 사용되는 특수한 타입.
interface를 쓴 다음, 객체 타입처럼 만들면 됩니다.interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean; // 옵셔널 프로퍼티
}
인터페이스 상속 방법
interface Name {
id: string;
name: string;
price: string;
}
interface Name2 : extends Name { //Name을 상속받음
}
인터페이스로 함수 정의
interface Fuc {
(print: Print) : void;
}
함수에 직접적으로 인터페이스 연결하기
const printProduct :PrintProductFunction = (product) =>{
console.log(`${product.name}의 가격은 ${product.price}원입니다.`);
}
리터럴 타입은 값을 타입으로 하는 타입.
let productName1 = '코드잇 블랙 후드'; // 타입 = string
const productName2 = '코드잇 텀블러'; // 타입 = 리터럴 타입
타입 별칭과 함께 주로 쓰임.
타입 별칭은 타입에 이름을 지정해줄 수 있는 시스템.
type Cart = string
타입 별칭은 모든 타입에 사용할 수 있음.
함수에 사용
type CartResultCallback = (result: boolean) => void;
const fuc : CartResultCallback = (result) {
console.log(result)
}
객체에 사용
type Product {
id: string;
name: string;
}
사실상 객체의 타입은 타입 별칭 과 인터페이스 사용이 가능함.
다만 객체는 인터페이스 를 추천한다고 함.
유니언 타입은 변수나 함수의 매개변수가 여러 개의 타입 중 하나일 수 있음을 나타내는 방법.
이는 다양한 타입의 값들을 하나의 변수에 허용할 수 있게 해주는 기능.
유니언 타입은 파이프(|) 문자를 사용하여 표현됨.
let value: string | number;
value = "Hello"; // string 타입의 값 할당 가능
value = 42; // number 타입의 값 할당 가능
함수에 유니언 타입 을 이용하면 매개변수를 특정 값들 중 하나일 수 있음을 나타낼 수 있음.
if 조건문을 이용하면 특정 타입에만 존재하는 프로퍼티를 활용할 수 있다.
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
interface ClothingProduct extends Product {
sizes: ClothingSize[];
color: string;
}
interface ShoeProduct extends Product {
sizes: number[];
handmade: boolean;
}
function printSizes(product: ClothingProduct | ShoeProduct) {
const availableSizes = product.sizes.join(", ");
console.log(`구매 가능한 사이즈는 다음과 같습니다: ${availableSizes}`);
if ("color" in product) {
console.log(`색상: ${product.color}`);
}
if (`handmade` in product) {
console.log(
product.handmade
? `이 상품은 장인이 직접 만듭니다.`
: `이 상품은 공장에서 만들어졌습니다.`
);
}
유니언 타입 은 모든 값에 사용할 수 있다.
특히 리터럴 값 이랑 같이 사용하면 유용하다.
type shoesSize {
size : 100 | 200 | 300 | 400 | 500;
}
const nikeShoes : shoesSize {
size: 100,
}
인터섹션 타입 은 타입을 합치는 방법.
&(앤드퍼센트) 을 사용해서 구현 가능하다.
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 Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
position: string;
}
type PersonEmployee = Person & Employee;
오류를 일으킬 수 있는 코드의 예시이다.
interface Id {
id: string;
}
interface Timestamp {
createdAt: Date;
updatedAt: Date;
}
type Product = Id & Timestamp & {
name: string;
price: number;
membersOnly?: boolean;
createdAt: string; // Timestamp 에 있는 중복된 프로퍼티이다.
updatedAt: string; // Timestamp 에 있는 중복된 프로퍼티이다.
};
이런 경우에는
Product 타입 의 기존 프로퍼티가 Timestamp 의 프로퍼티를 덮어 쓰게 된다.
타입스크립트에서의 오류는 발생하지 않지만
자바스크립트로 트랜스파일 되고나서 작동할 때 오류가 발생할 수 있다.
가급적이면 명확한 타입 설계가 권장된다.
수학에서 집합에 대해 배웠다면 생각해볼 게 있다.
두 타입을 Union 하면 왠지 두 타입의 형태를 합쳐야 할 거 같고,
Intersection을 하면 두 타입의 공통된 부분만 타입으로 될 것 같다.
하지만 타입스크립트에서는 그렇게 작동하지 않는다.
타입스크립트에서 타입은 Structural Subtyping이라는 규칙을 따른다.
쉽게 말해서 구조가 같으면 같은 타입이라고 판단하는 것이다. 예를 들어서 a라는 프로퍼티를 갖는 타입 A가 있다고 해보죠. 이 타입의 a 프로퍼티를 출력하는 printA()라는 함수가 있습니다. 이 함수를 아래 코드와 같이 { a: 'codeit' }, { a: 'codeit', b: 42 }, { a: 'codeit', b: 42, c: true }라는 객체로 실행해도 모두 올바른 타입입니다. 세 객체 모두에 a라는 프로퍼티가 있기 때문에 타입 A라고 판단하는 거죠. 이런 걸 Structural Subtyping, Structural Type System이라고 부릅니다.
interface A {
a: string;
}
interface B {
b: number;
}
function printA(arg: A) {
console.log(arg.a);
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printA(x);
printA(y); // 잘못된 타입
printA(z);
printA(w);
용어가 조금 어려워 보인다.
쉽게 생각해서 "같은 모양이 있으면 같은 타입이라고 판단한다"라고 이해할 수 있다.
앞으로의 내용을 설명하기 위해서 간단하게 아래와 같은 그림으로 나타내보겠다.
원 안에 있는 것들은 타입 A에 해당하는 객체들이다.
![[Pasted image 20240828062950.png]]
마찬가지로 타입 B까지 그림으로 표현하면 아래와 같이 표현할 수 있을 것이다.
![[Pasted image 20240828063012.png]]
앞에서 Union 타입은 "A 타입이거나 B 타입이다"라고 표현했다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같다. 그래서 { a: 'codeit' }과 { b: 42 }는 물론이고 { a: 'codeit', b: 42 }도 타입 A | B라고 사용할 수 있다.
![[Pasted image 20240828063520.png]]
interface A {
a: string;
}
interface B {
b: number;
}
function printAUnionB(arg: A | B) {
// 여기서는 타입 A | B
if ('a' in arg) {
// 여기 안에서는 타입 A
console.log(arg.a);
}
if ('b' in arg) {
// 여기 안에서는 타입 B
console.log(arg.b); // VS Code에서 arg에 마우스를 호버해 보세요.
}
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printAUnionB(x);
printAUnionB(y);
printAUnionB(z);
printAUnionB(w);
마찬가지로 Interesection을 살펴보자. 앞에서 A & B라고 하면 "A와 B 두 타입을 합친다"라고 간단히 표현했다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같다. { a, b } 두 프로퍼티를 모두 가지고 있는 타입들이 A & B라고 할 수 있을것이다. 그래서 객체의 모양만 본다면 마치 Intersection이 두 타입의 모양을 합치는 것처럼 생각할 수 있다.
![[Pasted image 20240828063739.png]]
interface A {
a: string;
}
interface B {
b: number;
}
function printAIntersectionB(arg: A & B) {
console.log(arg.a);
console.log(arg.b);
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printAIntersectionB(x); // 타입 오류
printAIntersectionB(y); // 타입 오류
printAIntersectionB(z);
printAIntersectionB(w);
이번 레슨에서는 Structural Subtyping이라는 개념을 통해서, Union과 Intersection에 대해 그림으로 더 자세하게 알아보았다. 간단하게는 A | B라고 하면 "A 타입이거나 B 타입이다", A & B라고 하면 "A 타입이랑 B 타입을 합친 것이다"라고 생각하면 대부분의 경우 문제없이 사용할 수 있겠지만. 혹시 앞으로 타입스크립트를 사용하면서 interface의 상속을 활용하거나, 여러 타입을 조합해서 복잡한 타입을 다루게 될 때 오늘 배운 이 내용을 토대로 한번 생각해 보시면 혼란을 줄이는데 도움이 될 것이다.
객체 타입의 모든 키를 문자열 리터럴 유니언 타입으로 반환하는 데 사용됨.
interface User {
id: number;
name: string;
email: string;
}
type UserKeys = keyof User; // 'id' | 'name' | 'email'
타입스크립트에서 typeof는 주로 두 가지 용도로 사용됨.
런타임에 값의 타입을 확인하는 것과, 컴파일 타임에 타입을 추론하는 것.
typeofTypeScript의 typeof는 JavaScript와 마찬가지로 런타임에 변수나 값의 타입을 확인함.
이는 주로 조건문에서 사용됨.
let x = "Hello, World!";
if (typeof x === "string") {
console.log("x는 문자열입니다.");
}
typeofTypeScript에서는 typeof를 사용하여 변수, 객체, 또는 함수의 타입을 가져올 수 있다.
이는 특정 값이나 변수의 타입을 참조하여 동일한 타입을 다른 곳에서 재사용할 때 유용함.
let someValue = "Hello, TypeScript";
let valueType: typeof someValue; // string 타입으로 추론됨
valueType = "Another string"; // OK
valueType = 42; // Error: Type 'number' is not assignable to type 'string'
아래 두 코드는 거의 같은 역할을 하는 코드이다.
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 코드를 자바스크립트로 트랜스파일링해 보면 다음과 같다.
"use strict";
var UserType;
(function (UserType) {
UserType["Admin"] = "admin";
UserType["User"] = "user";
UserType["Guest"] = "guest";
})(UserType || (UserType = {}));
const role = UserType.Admin;
console.log(role === UserType.Guest);
Enum은 별도의 자바스크립트 객체를 만들어서 그 객체를 사용한다.
UserType은 자바스크립트에서 아래와 같은 객체인 것이다.
{ Admin: 'admin', User: 'user', Guest: 'guest' }
예를 들어서 가능한 UserType 값들을 모두 활용해서 어떤 동작을 구현하고 싶다면 Enum을 써서 Object.keys()라는 함수를 사용해 볼 수 있을 것이다.
console.log(Object.keys(UserType)); // ['Admin', 'User', 'Guest']
반면에 타입 별칭은 타입스크립트에서만 의미 있는 코드이다.
그래서 Enum과 달리 자바스크립트로 트랜스파일 했을 때 추가로 객체 같은 걸 만들지 않고 단순히 값만 사용하는 코드가 만들어진다.
"use strict";
const role = 'admin';
console.log(role === 'guest');
대부분의 경우 Enum 또는 타입 별칭을 모두 사용할 수 있다.
하지만 되도록 Enum의 목적에 맞는 경우라면 Enum 문법을 사용하시는 걸 권장한다.
아래 두 코드는 거의 같은 역할을 하는 코드이다.
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의 상속과 Intersection의 가장 큰 차이점은 Intersection은 두 가지 이상의 타입을 한 번에 합칠 수 있다는 것이다. 이것도 Interface로 아주 불가능하지는 않다.
interface Entity {
id: string;
}
interface TimestampEntity extends Entity {
createdAt: Date;
updatedAt: Date;
}
interface User extends TimestampEntity {
username: string;
email: string;
}
type Id = {
id: string;
}
type Entity = {
createdAt: Date;
updatedAt: Date;
}
type User = Id & Entity & {
username: string;
email: string;
}
대부분의 경우 Interface와 타입 별칭을 둘 다 사용할 수 있을 것이다. 하지만 되도록 Interface의 목적에 맞는 경우라면 Interface를 사용하는 걸 권장한다.
타입 별칭은 타입에 '이름'을 정하는 문법이다. 복잡한 타입을 만들고, 그 타입을 여러 곳에서 활용할 때 사용하면 된다. 예를 들자면 아래처럼 복잡한 타입을 만들고 여러 곳에서 재활용할 수 있겠다.
type Point = [number, number];
type SearchQuery = string | string[];
type Result = SuccessResult | FailedResult;
type Coupon =
| PromotionCoupon
| EmployeeCoupon
| WelcomCoupon
| RewardCoupon
;
타입스크립트의 제네릭(Generics)은
재사용이 가능한 컴포넌트를 만들 수 있게 해주는 강력한 기능이다.
제네릭 을 사용하면 타입을 마치 함수의 매개변수처럼 다룰 수 있어, 코드의 타입을 미리 지정하지 않고도 다양한 타입에 대해 동작하도록 만들 수 있다.
제네릭을 사용하면 함수, 클래스, 인터페이스 또는 타입을 정의할 때
구체적인 타입을 미리 지정하지 않고, 그 타입을 매개변수화하여 나중에 사용자가 결정하게 할 수 있다.
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // T는 number로 추론됨
const str = identity<string>("hello"); // T는 string으로 추론됨
위 예제에서 identity 함수는 제네릭 함수로, T라는 타입 매개변수를 사용한다.
함수 호출 시 T가 어떤 타입인지 지정할 수 있으며, 이로 인해 다양한 타입에 대해 같은 함수를 재사용할 수 있다.
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 123 };
const stringBox: Box<string> = { value: "hello" };
여기서 Box 인터페이스는 T라는 제네릭 타입 매개변수를 가지고 있다. numberBox와 stringBox는 서로 다른 타입을 가지지만, 동일한 인터페이스 Box를 사용하여 정의된다.
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
const firstNum = getFirstElement([1, 2, 3]); // T는 number로 추론됨
const firstStr = getFirstElement(["a", "b", "c"]); // T는 string으로 추론됨
위 예제에서는 제네릭 함수 getFirstElement가 배열의 첫 번째 요소를 반환한다.
이 함수는 배열의 타입에 관계없이 동작하며, T를 통해 배열의 요소 타입을 추론한다.
때때로 제네릭 타입 매개변수가 특정 타입을 상속받도록 제한해야 할 때가 있다.
이를 제네릭 제약 조건이라고 한다.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("hello"); // OK, string은 length 속성을 가짐
getLength([1, 2, 3]); // OK, 배열도 length 속성을 가짐
// getLength(42); // 오류, number는 length 속성을 가지지 않음
위 코드에서 T extends { length: number }는
T가 length 속성을 가져야 한다는 제약 조건을 나타낸다.
TypeScript의 제네릭은 코드를 유연하고 재사용 가능하게 만들어주는 강력한 도구이다.
제네릭을 사용하면 다양한 타입에 대해 동작하는 재사용 가능한 컴포넌트(함수, 클래스, 인터페이스 등)를 작성할 수 있으며, 타입 안전성을 유지하면서도 중복을 줄이고 코드의 가독성을 높일 수 있다.
이번 파트에서는 타입스크립트에서 기본적으로 제공하는 제네릭 타입 중에서 알아두면 유용한 것들을 소개한다.
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);
Record객체에 키와 밸류 타입을 정해놓고 싶을 때 사용한다.
Map과 비슷하지만 차이점은 순수한 객체에 타입만 추가한다는 점이다.
const productMap: Record<string, Product> = {}
productMap['c001'] = product1;
productMap['c002'] = product2;
Pickinterface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
type ProductInfo = Pick<Product, 'name' | 'price'>;
Pick으로 만든 타입은 아래와 같다.
name 프로퍼티와 price 프로퍼티만 골라서 타입을 만들었다.
type ProductInfo = {
name: string;
price: number;
}
Omitinterface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
type ProductInfo = Omit<Product, 'id' | 'membersOnly'>;
Omit으로 만든 타입은 아래와 같다. id와 membersOnly를 제외하고 타입을 만들었다.
type ProductInfo = {
name: string;
price: number;
}
ExcludeUnion을 사용해서 PromotionCoupon 또는 EmployeeCoupon 또는 WelcomCoupon 또는 RewardCoupon인 타입을 Coupon이라고 했다. 여기서 EmployCoupon에 해당하는 것만 제거를 하고 싶을 때 Exclude를 사용할 수 있다.
type Coupon =
| PromotionCoupon
| EmployeeCoupon
| WelcomCoupon
| RewardCoupon
;
type InternalCoupon = EmployeeCoupon;
type PublicCoupon = Exclude<Coupon, InternalCoupon>;
// type PublicCoupon = PromotionCoupon | WelcomCoupon | RewardCoupon
Parameters함수 파라미터들의 타입을 함수의 타입을 통해 정의할 수 있다.
만약 함수의 타입이 아니라, 선언된 함수라면 typeof 연산자로 함수의 타입으로 만들어서 사용하면 된다.
function addToCart(id: string, quantity: number = 1): boolean {
// ...
return true;
}
type AddToCartParameters = Parameters<typeof addToCart>;
// type AddToCartParameters = [id: string, quantity: number | undefined]
ReturnTypeParameters와 마찬가지로 함수의 리턴 타입을 가져온다.
function addToCart(id: string, quantity: number = 1): boolean {
// ...
return true;
}
type AddToCartResult = ReturnType<typeof addToCart>;
// type AddToCartResult = boolean
tsconfig.json 모듈 사용하기tsconfig.jsonTypeScript 프로젝트의 구성 설정 파일로, TypeScript 컴파일러(tsc)의 동작 방식을 정의하는 데 사용됨.
compilerOptions에서 사용할 수 있는 옵션 중에서 중요한 것들을 살펴보겠다.
target: 어떤 ECMAScript 버전으로 변환할지타입스크립트 코드는 빌드해서 자바스크립트로 변환한다.
이때 변환할 자바스크립트의 버전을 정할 수 있다.
만약에 프로젝트가 과거 자바스크립트 표준만 지원하는 브라우저까지
지원해야 한다면, 여기서 오래된 버전을 설정할 수 있다.
2023년 현재를 기준으로 tsconfig.json을 생성했을 때는, 기본적으로는 ES7(ES2016)으로 변환하도록 되어있다.
코드를 실행할 브라우저나 Node.js 환경에 따라 선택하시면 된다 브라우저의 경우 ES5는 거의 모든 브라우저에서 지원하고, ES6는 인터넷 익스플로러를 제외하면 대부분의 브라우저에서 지원한다. Node.js의 경우 v14 이후 버전을 쓰신다면 ES5, ES6를 대부분 지원한다.
잘 모르겠다면 일단은 ES6로 설정해 두고 쓰시는 걸 추천한다. 실행 환경에 따른 문법 지원 여부가 궁금하신 분들은 아래 두 링크를 참고.
module: 어떤 방식으로 모듈을 만들지자바스크립트 모듈에는 크게 두 가지 방식이 있다. ES6부터 도입된 import/export 문법을 사용하는 ESM(ECMAScript Module) 방식이 있고, Node.js 같은 데서 기본적으로 사용하는 CJS(CommonJS) 방식이 있다.
이 옵션에서는 자바스크립트 코드로 트랜스파일할 때 어떤 모듈 문법으로 변환할지 선택할 수 있다. 다양한 옵션 값을 지원한다. ESM을 쓰시려면 es6, es2020 같이 es로 시작하는 값을 쓰면 되고, CJS를 쓰시려면 commonjs라고 쓰면 된다..
보통 Node.js 환경에서는 CJS를 사용하고, 프론트엔드 개발을 할 때는 보통 번들러에서 모듈을 알아서 처리해 주기 때문에 ESM, CJS 상관없이 쓰실 수 있을 것이다. 어떤 걸 선택할지 잘 모르겠다면 commonjs로 설정하는 걸 추천.
esModuleInterop: ES 모듈을 안전하게 사용ESM 문법에서 import * as moment from 'moment'라든가 import moment from 'moment'라는 문법은 서로 다른데요. 이 옵션을 false로하면 CJS로 변환했을 때 두 코드는 같은 형태의 코드 const moment = require('moment')로 변환됩니다. 안전하게 모듈을 사용하려면 esModuleInterop 옵션은 true로 해놓는 것을 권장드립니다.
forceConsistentCasingInFileNames: 파일의 대소문자 구분하기macOS를 사용하시는 분들은 main.ts라는 파일과 Main.ts라는 파일이 서로 다른데요. Windows와 같이 어떤 운영체제에서는 종종 main.ts라는 파일과 Main.ts라는 파일을 동일하게 취급하기도 합니다. 이런 환경에서 개발하더라도 반드시 대소문자 구분을 명확하게 하겠다는 옵션입니다. 이 옵션도 반드시 true로 해 놓는 걸 권장드립니다.
strict: 엄격한 규칙들 켜기이 옵션을 켜면 stict와 관련된 여러 규칙들을 한꺼번에 켤 수 있다.
어떤 옵션들이 켜지는지는 공식 문서의 strict 옵션 설명을 참고. 여기서는 그중에서도 대표적으로 알아두면 좋은 것 두 가지를 소개한다.
noImplicitAny: 아무 타입을 직접 정의하지 않고, 타입 추론도 되지 않는 상태를 implicit any라고 한다. 쉽게 말해서 기존 자바스크립트 코드처럼 타입 없이 사용하는 걸 implicit any라고 한다.function printSize(product) {
~~~~~~~ 타입 정의도 없고 추론할 수도 없음
console.log(product.size);
}
새로 시작하는 타입스크립트 프로젝트라면 무조건 켜는 걸 추천드리지만, 기존에 자바스크립트로 만든 프로젝트를 타입스크립트로 옮기는 중이라면 이 옵션을 잠시 끄는 것도 좋다. 아래처럼 설정하면 strict 규칙들을 한꺼번에 설정하지만 noImplicitAny는 설정하지 않을 수 있다.
"strict": true,
"noImplicitAny": false,
strictNullChecks: null이 될 가능성이 있다면, 이런 경우를 반드시 처리하도록 하는 옵션이다. 이것도 되도록이면 켜 놓으시는 걸 추천한다. 예를 들자면 아래와 같은 코드는 strict null check를 할 때는 오류가 난다. num 변수가 null이 될 수도 있으니. 반면에 strictNullChecks가 꺼져있다면 타입 오류가 나지 않는다. 대신에 실행하는 도중에 num 변수에 null 값이 들어간다면 런타임 오류가 발생한다.
let num: number | null; // ... num -= 1; ~~~
되도록이면 strictNullChecks를 켜고, 이런 경우에는 반드시 null check를 하는 걸 권장한다.
let num: number | null;
// ...
if (num !== null) {
num -= 1;
}
skipLibCheck: 설치한 패키지의 타입 검사하지 않기node_modules 폴더에 설치된 패키지들의 타입 검사를 하지 않는 옵션. 패키지 개발 과정에서 대부분 타입 검사가 이뤄지기 때문에, 중복으로 하지 않아도 됨. 그래서 이 옵션을 사용하시길 추천.
rootDir: 최상위 폴더타입스크립트 컴파일러가 처리할 타입스크립트 파일들의 최상위 폴더를 정하는 옵션이다. 기본 값으로는 처리하는 파일들의 경로를 종합해서 최상위 폴더를 정한다. 만약 tsconfig.json 파일과 같은 폴더에 있는 src 폴더를 최상위 폴더로 정하고 싶다면 아래와 같이 쓰면 된다.
{
"compilerOptions": {
"rootDir": "src",
}
}
outDir: 자바스크립트 파일을 생성할 폴더outDir에 지정된 폴더 안에다가 rootDir의 디렉토리 구조에 따라서 자바스크립트 파일을 만든다. 값을 지정하지 않으면 소스코드 파일과 같은 폴더에 자바스크립트 파일을 만든다.
{
"compilerOptions": {
"outDir": "dist",
}
}
include와 excludetsc로 트랜스파일할 때 포함할 경로(include)와 포함하지 않을 경로(exclude)를 정해줄 수 있다. 배열로 경로 패턴을 적어주면 된다. **/*라는 코드는 아래의 모든 폴더, 모든 파일을 의미함. 참고로 이런 패턴을 Glob 패턴이라고 함. 혹시 궁금하신 분들은 위키피디아 Glob 문서를 참고.
{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
"include": ["src/**/*", "tests/**/*"],
"exclude": ["bin/**/*"],
}
옵션이 너무 많기 때문에 모든 옵션들을 전부 알고 있을 필요는 없다. 그 밖의 다른 옵션들이 궁금하신 분들은 타입스크립트 공식 문서를 참고하시는 걸 추천한다.
참고로 VS Code에서는 옵션들을 자동완성 기능으로 작성할 수 있다. 무엇을 작성해야 할지 모를 때 Windows에서는 Ctrl + I, macOS에서는 Cmd + I를 입력하면 Suggestion이 뜬다. 각 옵션에 대한 설명도 볼 수 있으니까 공부 목적이 아니라 프로젝트를 개발할 때는 이 기능을 활용하시는 걸 추천한다.