const names: Array<string> = []; // string[]과 동일, Array<T>
const names2: Array<string | number> = []; // (string | number)[]
let promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved!"); // 1초 후에 Promise를 Fulfilled 상태로 변경
}, 1000);
});
promise.then((message) => {
console.log(message); // "Promise resolved!"
}).catch((error) => {
console.log("Promise rejected with error: " + error);
});
Promise는 JavaScript와 TypeScript에서 비동기 작업을 처리하기 위한 객체입니다. Promise는 비동기 작업의 최종 완료(또는 실패)와 그 결과 값을 나타냅니다.
Promise는 다음 세 가지 상태 중 하나를 가질 수 있습니다:
- Pending(대기 중): 비동기 처리가 아직 완료되지 않은 상태입니다.
- Fulfilled(이행됨): 비동기 처리가 성공적으로 완료된 상태입니다.
- Rejected(거부됨): 비동기 처리가 실패하거나 오류가 발생한 상태입니다.
let promise: Promise<number> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved!");
}, 1000);
});
promise.then((data) => {
data.split(' '); // ERROR, 반환값에 대한 타입이 TypeScript의 지원을 받을 수 있게 됨
}).catch((error) => {
console.log("Promise rejected with error: " + error);
});
split()은 string에서만 가능.
let promise = new Promise<string>((resolve, reject) => {...});
// Promise가 처리하는 값이 문자열이라는 것을 TypeScript가 알 수 있음
let promise: Promise<string> = new Promise((resolve, reject) => {...});
//변수 `promise`가 문자열을 처리하는 Promise 객체를 저장해야 한다는 것을 TypeScript가 알 수 있음
둘 다 동일한 작업을 함, 코드 선호도에 따라 결정.
function merge(objA: object, objB: object) {
return Object.assign(objA, objB); // assgin()은 객체를 합치는 메서드
}
const mergedObj = merge({name: 'Max'}, {age: 30});
console.log(mergedObj.age); // ERROR, 객체는 합쳐졌지만 TypeScript는 알 수 없음.
object에 어떤 타입이, 얼마나 들어갈지 모르기 때문에 문제가 생김.
function merge<T, U>(objA: T, objB: U) {
return Object.assign(objA, objB);
}
const mergedObj = merge({name: 'Max', hobbies: ['Sports']}, {age: 30});
console.log(mergedObj.age); // 30
제네릭 함수는 동적으로 설정 됨
const mergedObj = merge({name: 'Max', hobbies: ['Sports']}, {age: 30});
const mergedObj = merge<{name:string, hobbies: string[]}, {age: number}>
{name: 'Max', hobbies: ['Sports']}, {age: 30});
제네릭 함수를 사용하게 되면 위와 같은 효과를 나타나게 됨. 실제로 아래 코드도 잘 작동하게 됨.
Type Guard로 인한 에러, extends로 제약을 걸어 해결
function merge<T extends object, U extends object>(objA: T, objB: U): T & U { return Object.assign({}, objA, objB); // Object.assign(objA, objB);는 objA에 복사 // Object.assign({}, objA, objB);는 새로운 객체에 복사 } const mergedObj = merge({name: 'Max', hobbies: ['Sports']}, {age: 30}); const mergedObj1 = merge({name: 'Max', hobbies: ['Sports']}, 30}); // ERROR
T & U를 사용하는 이유는, 입력으로 받은 두 객체 objA와 objB의 모든 속성을 포함하는 새로운 객체를 반환하기 때문입니다.
예를 들어, T가 { name: string; }이고 U가 { age: number; }라면, T & U는 { name: string; age: number; }가 됩니다. 따라서 merge 함수는 name 속성과 age 속성을 모두 가진 객체를 반환합니다.
interface Lengthy {
length: number;
}
function countAndDescribe<T extends Lengthy>(element: T): [T, string] {
let descriptionText = 'Got no value.';
if (element.length === 1) {
descriptionText = 'Got 1 element.';
} else if ( element.length > 1) {
descriptionText = 'Got ' + element.length + ' elements.';
}
return [element, descriptionText];
}
console.log(countAndDescribe('Hi there!'));
// [ 'Hi there!', 'Got 9 elements.' ]
console.log(countAndDescribe(['Sports', 'Cooking']));
// [ [ 'Sports', 'Cooking' ], 'Got 2 elements.' ]
console.log(countAndDescribe(10)); // ERROR
Lengthy인터페이스의length속성은element가 문자열이나 배열과 같이length속성을 가진 타입일 경우 그length속성에 접근하기 위해 사용됩니다.
(element: T)를 통해 매개변수의 입력 타입을 결정할 수 있음.
function extractAndConvert(obj: object, key: string) {
return 'Value: ' + obj[key]; // ERROR, obj에 key가 있을 보장이 없기 때문.
}
extractAndConvert({}, 'name');
function extractAndConvert<T extends object, U extends keyof T>(obj: T, key: U) {
return 'Value: ' + obj[key];
}
extractAndConvert({}, 'name'); // ERROR, 객체에 'name' 키가 없기 떄문
extractAndConvert({name: 'Max'}, 'name');
extractAndConvert({name: 'Max'}, 'age'); // ERROR, 객체에 'age' 키가 없기 떄문
위처럼 Generic 타입과 keyof를 사용하면 예외적인 상황이나 개발자의 실수를 사전에 미리 방지할 수 있게 됨
keyof 적용하기type User = {
name: string;
age: number;
};
type UserKeys = keyof User; // "name" | "age"
keyof 적용하기interface Product {
id: number;
name: string;
price: number;
}
type ProductKeys = keyof Product; // "id" | "name" | "price"
keyof 적용하기class Car {
model: string;
year: number;
}
type CarKeys = keyof Car; // "model" | "year"
keyof 적용하기type ArrayKeys = keyof Array<string>; // number | "length" | "toString" | "pop" | "push" | "concat" | "join" | ...
keyof를 적용하려고 하면 함수의 속성에 대한 키를 얻습니다type FunctionKeys = keyof Function; // "apply" | "call" | "bind" | "prototype" | "length" | "name" | ...
typeof: typeof 연산자는 변수의 타입을 추출합니다. JavaScript에서 typeof는 실행 시간에 변수의 타입을 반환하지만, TypeScript에서는 컴파일 시간에 변수의 타입을 추출합니다. 따라서 TypeScript에서 typeof는 변수, 객체, 배열 등의 타입을 가져오는 데 사용됩니다.let num = 123;
type NumType = typeof num; // number
keyof: keyof 연산자는 객체의 속성 키를 추출합니다. 이는 주로 객체의 속성 이름을 타입으로 만드는 데 사용됩니다. keyof를 사용하면 객체의 속성 이름을 문자열 또는 심볼 타입으로 추출할 수 있습니다.type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // "name" | "age"
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return [...this.data];
}
}
const stringStorage = new DataStorage<string>();
stringStorage.addItem('asdf');
stringStorage.addItem('qwer');
stringStorage.removeItem('asdf');
console.log(stringStorage.getItems()); // [ 'qwer' ]
const numberStorage = new DataStorage<number>();
numberStorage.addItem(12);
numberStorage.addItem(34);
numberStorage.removeItem(12);
console.log(numberStorage.getItems()); // [ 34 ]
여기서 문제는 this.data.splice(this.data.indexOf(item), 1); 이 방식을 제거하는 방법은
string | number | boolean에서만 가능하며 object 타입으로 진행하거나 값을 못 찾는 경우에는 -1을 리턴하여 맨 마지막 요소를 제거 하기 때문에 문제가 있음 (메모리 접근)
이 문제는 TypeScript와는 별개이므로 설명하지 않겠음.
Pick 타입은 기존에 있던 타입에서 필요한 것만 골라서 새로운 타입을 만들어주는 건데요.
Pick 유틸리티 타입은 우리가 고른 거 외에는 전부 제거합니다.
type Student = {
name: string
lastName: string
age: number
class: string
}
type SomeStudent = Pick<Student, "name" | "age">
// type SomeStudent = {
// name: string;
// age: number;
// }
Omit 유틸리티 타입은 Pick 유틸리티 타입과 정반대로 작동됩니다.
삭제할 Keys 항목만 넣으면 그거 말로 나머지를 리턴해 주는 타입입니다.
기존 타입에서 일부만 제거하려고 할 때 아주 유용합니다.
type Student = {
name: string
lastName: string
age: number
class: string
}
type SomeStudent = Omit<Student, "lastName" | "class">
// type SomeStudent = {
// name: string;
// age: number;
// }
Readonly 유틸리티 타입은 작성된 값이 변경할 수 없게 만들어 줍니다.
Readonly로 새롭게 생긴 타입에 새로운 값을 할당하려고 하면 타입스크립트 경고가 나옵니다.
type Student = {
name: string,
}
type ReadOnlyStudent = Readonly<Student>
const student: ReadOnlyStudent = {
name: 'Jin',
}
student.name = 'Jung'
// Cannot assign to 'name' because it is a read-only property.
네번 째인 Partial 유틸리티 타입은 기존 타입의 항목을 전부 옵셔널(optional)로 만들어 줍니다.
이 유틸리티 타입은 우리가 받은 객체가 아직 뭔지 모를 때 아주 유용합니다.
type Student = {
name: string
lastName: string
age: number
class: string
}
type PartialStudent = Partial<Student>
// type PartialStudent = {
// name?: string | undefined;
// lastName?: string | undefined;
// age?: number | undefined;
// class?: string | undefined;
// }
Required 타입은 Partial 유틸리티 타입과 정반대로 작동합니다.
모든 옵셔널 상태인 항목에 대해 옵셔널 상태를 없애주거든요.
type Student = {
name?: string
lastName?: string
age?: number
class?: string
}
type RequiredStudent = Required<Student>
// type RequiredStudent = {
// name: string;
// lastName: string;
// age: number;
// class: string;
// }
TypeScript의 Record 유틸리티 타입은 객체의 키와 값에 대한 타입을 지정하는데 사용됩니다. Record는 두 개의 타입 매개변수를 받으며, 첫 번째 매개변수는 키의 타입, 두 번째 매개변수는 값의 타입을 나타냅니다.
Record의 기본 형태는 다음과 같습니다:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
예를 들어, Record<string, number>는 모든 키가 문자열이고 모든 값이 숫자인 객체의 타입을 나타냅니다:
const obj: Record<string, number> = {
prop1: 1,
prop2: 2,
prop3: 3,
// prop4: "four" // 이 줄은 오류를 발생시킵니다. 값이 숫자가 아닙니다.
};
또한, Record는 열거 가능한 키의 집합을 사용하여 객체의 타입을 지정하는 데도 사용될 수 있습니다:
type RGB = "red" | "green" | "blue";
const colors: Record<RGB, string> = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF",
};
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return [...this.data];
}
}
class DataStorage {
private data: string[] | number[] | boolean[] = [];
addItem(item: string | number | boolean) {
this.data.push(item); // ERROR
}
removeItem(item: string | number | boolean) {
this.data.splice(this.data.indexOf(item), 1); // ERROR
getItems() {
return [...this.data];
}
}
제네릭 타입과 유니온 타입과 유사하게 허용된 타입을 설정해줄 수 있지만,
제네릭은 동적으로 설정되기에 내부 함수에 대해서 제한을 둘 수 있고,
유니온 함수는 추가적인 작업을 필요로 할 수 있다.