타입을 마치 함수의 파라미터처럼 사용하는 것
/**
* 첫번째 사례
* 두 매개변수의 타입이 다를 때는 제네릭을 여러 개 써줄 수 있다.
*/
function swap<T, U>(a: T, b: U) {
return [b, a];
}
const [a, b] = swap("1", 2);
/**
* 두번째 사례
*/
function returnFirstValue<T>(data: T[]) {
return data[0];
}
let num = returnFirstValue([0, 1, 2]);
let str = returnFirstValue([1, "hello", "my"]);
function returnFirstValue<T>(data: [T, ...unknown[]]) {
return data[0];
}
let num = returnFirstValue([0, 1, 2]);
let str = returnFirstValue([1, "hello", "my"]);
unknown 타입의 배열이 들어올것 같아
라고 표기해주는 것이다./**
* 세번째 사례
*/
function getLength<T>(data: T){
return data.length //오류 'T' 형식에 'length' 속성이 없습니다.ts(2339)
}
let var1 = getLength([1,2,3]);
let var2 = getLength("12345")
let var3 = getLength({length: 10})
function getLength<T extends { length: number}>(data: T){
return data.length
}
let var1 = getLength([1,2,3]);
let var2 = getLength("12345")
let var3 = getLength({length: 10})
<T extends { length: number}>
length가 number인 프로퍼티를 가지고 있는 객체를 확장하는 타입
이라 표기해준다. 이는 인터페이스를 생각해보면 이해하기 쉽다.
interface A {
length: number;
}
interface B extends A {}
// B는 A를 확장했기에, 무조건 length 프로퍼티를 갖고 있어야한다.
const arr = [1, 2, 3];
const newArr = arr.map((it) => it * 2); // [2, 4, 6]
function map(arr, callback) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
map(arr, (it) => it * 2);
정의한 map을 보면
it
는 arr의 타입에 따라 간다. arr가 number 배열이라면 it는 number이고, arr가 string 배열이라면 it는 string인 것이다.
function map<T>(arr : T[], callback : (item : T) => T) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
map(["hi,", "hello"], (it) => parseInt(it));
function map<T, U>(arr : T[], callback : (item : T) => U) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
function forEach<T>(arr: T[], callback: (item: T) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i]);
}
}
void
로 지정해줘도 된다./**
* 제네릭 인터페이스
*/
// K, V와 같은 것을 "타입변수"라고 부르고, 문서에 따라
// "타입 파라미터", "제네릭 타입 변수", "제네릭 타입 파라미터"라 불리기도 한다.
interface KeyPair<K, V> {
key: K;
value: V;
}
// 제네릭 인터페이스를 타입으로 사용할 땐, 타입 변수에 할당할 타입을 꺽쇄와 함께 타입을 작성해야한다.
let keyPair: KeyPair<string, number> = {
key: "key",
value: 1,
};
let keyPair2: KeyPair<boolean, string[]> = {
key: true,
value: ["1", "2"],
};
/**
* 인덱스 시그니쳐
*/
interface NumberMap {
[key: string]: number;
}
let numberMap1: NumberMap = {
key: 444,
};
interface Map<V> {
[key: string]: V;
}
let stringMap: Map<string> = {
key: "value",
};
let booleanMap: Map<boolean> = {
key: true,
};
/**
* 제네릭 타입 별칭
*/
type Map2<V> = {
[key: string]: V;
};
let stringMap2: Map2<string> = {
key: "hello",
};
interface Student {
type: "student"
school: string;
}
interface Developer {
type: "developer";
skill : string;
}
interface User {
name: string;
profile : Student | Developer
}
const developerUser : User = {
name: "kang",
profile : {
type: "developer",
skill: "TypeScript"
}
}
const studentUser : User = {
name: 'kim',
profile: {
type: "student",
school: "blahblah"
}
}
function goToSchool(user: User){
if(user.profile.type !== 'student'){
console.log('wrong')
return;
}
const school = user.profile.school;
console.log(`${school} 등교 완료`)
}
interface User<T> {
name: string;
profile : T
}
function goToSchool(user: User<Student>) {
const school = user.profile.school;
console.log(`${school} 등교 완료`);
}
const developerUser: User<Developer> = {
name: "kang",
profile: {
type: "developer",
skill: "TypeScript",
},
};
const studentUser: User<Student> = {
name: "kim",
profile: {
type: "student",
school: "blahblah",
},
};
class NumberList {
constructor(private list: number[]) {}
push(data: number) {
this.list.push(data);
}
pop() {
return this.list.pop();
}
print() {
console.log(this.list);
}
}
const numberList = new NumberList([1, 2, 3]);
numberList.pop();
numberList.push(4);
numberList.print();
list: string[]
으로 바꿔주고 해야될까? 아니다. 제네릭을 활용하면 된다 !class List<T> {
constructor(private list: T[]) {}
push(data: T) {
this.list.push(data);
}
pop() {
return this.list.pop();
}
print() {
console.log(this.list);
}
}
const numberList = new List([1, 2, 3]);
numberList.pop();
numberList.push(4);
numberList.print();
const stringList = new List(["wow", "good"]);
<string>
(["wow", "good"]); 처럼 적을 필요가 없다. 인수에 들어가는 배열로 Constructor에서 타입 추론을 하기 때문. new <T>(executor: (resolve: (value: T | PromiseLike<T>)
=> void, reject: (reason?: any) => void) => void): Promise<T>;
resolve는 제네릭을 따라가지만, reject는 any로 되어있다.
예를들어 Post 하는 함수가 있다고 하자. 인터페이스는 다음과 같이 정의되어있다.
interface Post {
id: number;
title: string;
content: string;
}
fetchPost
함수는 아래와 같다.function fetchPost(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
title: "amazing Title",
content: "amazing Content",
});
}, 3000);
});
}
fetchPost
를 통해 then으로 id를 받으려하면? 에러가 발생한다.const postRequest = fetchPost()
postRequest.then((post) => {
post.id //Error
})
1. new Promise에 타입작성하기
function fetchPost() {
return new Promise<Post>((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
title: "amazing Title",
content: "amazing Content",
});
}, 3000);
});
}
2. 함수 반환값 타입에 작성하기
- 함수의 선언 부분만 보고도 어떤 상태를 반환하는지를 알 수 있기에 함수 반환값에 정의하는 것을 더 추천한다.
function fetchPost() : Promise<Post> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
title: "amazing Title",
content: "amazing Content",
});
}, 3000);
});
}