제네릭은 선언 시점이 아닌 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법입니다.
제네릭을 선언할 때 관용적으로 사용되는 대표적인 식별자로
T
가 있고, 그 외에U
와V
가 있습니다. 반드시T
,U
,V
를 사용하여 하는 것은 아니지만 관용적인 식별자를 쓰는게 모범적입니다.
어떻게 보면 어떤 타입을 전달해도 사용이 가능한 any
랑 다른점이 있을지 고민이 될수도 있지만 any
는 타입체크를 전혀 하지 않아서 전달받은 데이터의 타입을 알 수 없고 반환할 때 타입의 정보를 반환하지 않습니다. 이런 반면에 제네릭은 전달받은 타입을 확인 및 반환을 할 수 있고 타입을 제한 할 수도 있습니다.
인터페이스 및 타입에도 제네릭 타입을 적용하여 타입의 프로퍼티에 보다 유연하게 타입을 입힐 수 있습니다.
제네릭 타입에 기초값을 지정할수도 있는데, 기초값이
string
으로 지정을 해서stringObject2
상수의 타입인IMyInterfaceG
에<string>
을 명시해주지 않아도 됩니다.
// No Generic - object선언할때 타입이 명시가 안됨! 타입추가도 interface에서 해줘야 함
interface IMyInterface {
value: string | number | Array<string>;
}
const stringObject: IMyInterface = { value: "hello, world!" };
const numberObject: IMyInterface = { value: 1234 };
const stringArrayObject: IMyInterface = { value: ["hello", "world!"] };
// Generic - 반대로 타입을 명시할 수 있고, 타입추가도 따로 안해줘도 되며, 기초값도 줄 수 있음
interface IMyInterfaceG<T = string> {
value: T;
}
const stringObject2: IMyInterfaceG = { value: "hello, world!" };
const numberObject2: IMyInterfaceG<number> = { value: 1234 };
const stringArrayObject2: IMyInterfaceG<Array<string>> = {
// IMyInterfaceG<string[]> 기능적으로는 동일
value: ["hello", "world!"],
};
앞서 69일차의 app.js를 ts로 만들어 볼 때 깨달았던 부분인데
Array<string>
과string[]
은 기능적으로 차이가 없다!키워드 사용할 때 조금 다른데 대표적으로
readonly
같은 경우에는 배열 및 튜플 리터럴 형식에서만 사용할 수 있어 에러가 발생한다!그렇다고 제너릭형식은
readonly
를 사용하지 못하냐? 그건 또 아니다.
ReadonlyArray
라는 유틸리티함수사용하면 에러가 발생하지 않는다!
interface IMyInterface {
value: string | number | Array<string>;
}
// 기능적으로는 동일
interface IMyInterface2 {
value: string | number | string[];
}
// 키워드 사용할 때 조금 다름
// const error: readonly Array<boolean> = [false]; // error
// 'readonly' 형식 한정자는 배열 및 튜플 리터럴 형식에서만 사용할 수 있습니다.
const okay: readonly boolean[] = [true];
// generic과 readonly를 같이 사용하기 위해서는 ReadonlyArray 유틸리티함수사용
const okayGeneric: ReadonlyArray<boolean> = [true];
type User = {
email: string;
name: string;
};
function getData<T>(data: T): T {
return data;
}
// 에러 없이 콘솔로그 되는 유효한 호출
console.log(getData<string>("string data"));
console.log(getData<number>(1234));
console.log(getData<User>({ email: "email@email.com", name: "katie" }));
console.log(getData<string[]>(["string", "data"]));
console.log(getData<string[]>([])); // 빈 배열도 유효한 인자입니다!
타입스크립트에서 우리가 자주 사용하는 자바스크립트 메소드가 어떤 타입을 기대하고, 또 어떤 타입을 반환하고 있는지
enum Status {
Initiated = "Initiated",
Pending = "Pending",
Shipped = "Shipped",
Delivered = "Delivered",
}
interface Order {
buyer: string;
orderStatus: Status;
}
// ❌ Type 'string' is not assignable to type 'Status'.
const orders: Order[] = Object.keys(Status).map((status, index) => {
return {
buyer: `buyer #${index}`,
orderStatus: status,
};
});
❌ Type 'string' is not assignable to type 'Status'.
해결방법
=> Object.keys() 가 string반환
=>keys
를values
로 바꿔주면 Status반환!
Object.values(Status)도 가능
enum Status {
Initiated = "Initiated",
Pending = "Pending",
Shipped = "Shipped",
Delivered = "Delivered",
}
interface Order {
buyer: string;
orderStatus: Status;
}
const orders: Order[] = Object.values(Status).map((status, index) => {
return {
buyer: `buyer #${index}`,
orderStatus: status,
};
});
console.log(orders);
// const ordersE: Order[] = Object.entries(Status).map((status, index) => {
// return {
// buyer: `buyer #${index}`,
// orderStatus: status,
// };
// });
stack 데이터 구조를 클래스와 인터페이스를 이용하여 구현!
먼저 Stack이라는 클래스에 속성해 있어야 될 주요 함수들을 인터페이스로 정의를 합니다. 그 다음에 클래스를 본격적으로 써볼건데요, 이렇게 먼저 인터페이스를 선언 하고 클래스에 implements 를 해주면 인스턴스 함수들이 오토컴플릿이 되는걸 목격할 수 있습니다.
interface IStack<T> {
push(item: T): void;
pop(): T | undefined;
peek(): T | undefined;
size(): number;
}
class Stack<T> implements IStack<T> {
private storage: T[] = [];
constructor(private capacity = 4) {}
// constructor : const stack = new Stack<string>(); 을 했을때 실행됨
push(item: T): void {
if (this.size() === this.capacity) {
throw Error("stack is full");
}
this.storage.push(item);
}
pop(): T | undefined {
return this.storage.pop();
}
peek(): T | undefined {
return this.storage[this.size() - 1];
}
size(): number {
return this.storage.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
numberStack.push(100);
console.log(numberStack.peek()); // 100
console.log(numberStack.size()); // 4
numberStack.push(101); // ❌ Error: stack is full
push(item: T): void
함수는 storage
배열에 인자로 받는 item
값을 담아주는 함수입니다. 반환하는 값을 받고싶지 않아서 반환 타입을 void라고 설정을 했습니다. 추가로 capacity
라는 private
클래스 상수를 새로운 스택이 형성 될때마다 선언이 되도록 했는데요, push
를 했을 때 스택이 수용할 수 있는 제한을 둔겁니다. 그래서 우리가 101
을 담으려고 하면 stack is full
이라는 에러를 받습니다. capacity
를 설정하지 않고 class
를 설립할 수 있으니 원하시면 private capacity = 4
를 지우시면 됩니다.
pop() :T | undefined
는 storage
배열의 제일 마지막에 위치한 값을 뽑아내는거죠. undefined
는 storage
가 비어있을 때를 위해 반환값이므로 설정해줍니다.
peek(): T | undefined
도 pop()
과 마찬가지로 storage
배열의 제일 마지막 index
에 위치한 값을 다루는 함수인데요, storage
배열이 비어있을수도 있으니 대비해서 undefined
를 반환값으로 T
와 유니언 타입으로 설정해줍니다.
마지막으로 size(): number
함수는 storage
의 배열의 길이를 반환합니다, 즉 숫자를 반환하는 것이죠.
// public, private, protected
public => 제한없음, 누구나 access 가능
private => 특정 class 안에서만 access 가능
protected => 특정 class 와 상속받는 class 안에서만 access 가능
class Base {
first = ""; // 안적으면 자동으로 public
public second = "";
protected third = "";
private fourth = "";
baseFunction() {
this.first;
this.second;
this.third;
this.fourth;
}
}
class Inherited extends Base {
myFunction() {
this.first;
this.second;
this.third;
// this.fourth; // error
}
}
const inherited = new Inherited();
inherited.first;
inherited.second;
// inherited.third; // error
// inherited.fourth; // error