
클래스는 객체지향형 프로그래밍의 기본 단위로서 객체를 생성하는 템플릿과 같은 기능을 한다.
JavaScript는 Prototype에 기초한 클래스 개념을 가지고 있는데, 이는 C#, Java와 같은 객체지향형 프로그래밍 언어를 사용하는 개발자에게 익숙하지 않은 개념이다.
TypeScript에서는 C#, Java에서 사용하는 클래스 개념과 유사한 클래스를 사용하며, class 라는 키워드 사용하여 클래스를 구현한다.



타입스크립트는 객체의 특정 속성의 접근과 할당에 대해 제어가 가능하다. 이를 위해선 해당 객체가 클래스로 생성한 객체여야 한다.
✅ 일반적으로는 속성에 값을 대입하는 것이 쉽게 가능하다.

✅ 만약 속성에 제약사항을 추가하고 싶다면 get과 set을 활용하자

추상 클래스는 인터페이스와 비슷한 역할을 하면서도 조금 다른 특징을 갖고 있다.
추상 클래스는 특정 클래스의 상속 대상이 되는 클래스이며 좀 더 상위 레벨에서 속성, 메서드의 모양을 정의한다.

Class & TS : https://heecheolman.tistory.com/65
제네릭이란 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다. 특히 한가지 타입보다 여러가지 타입에서 동작하는 컴포넌트를 생성할 때 사용한다.
💡 제네릭은 타입을 마치 함수의 파라미터처럼 사용할 수 있다.
우리는 함수를 정의하고 파라미터로 값을 전달하여 결과값을 return 받을 수 있다.
function getText(text) {
return text;
}
getText('hi'); // 'hi'
getText(10); // 10
getText(true); // true
TypeScript을 적용하여 타입을 정의하고 싶어서 각 타입마다의 함수를 생성했다. 물론 함수호출에 따른 타입들을 만족하겠지만 코드가 길어지며, 복잡한 코드에서는 가독성이 떨이진다.
function getText1(text:string):string {
return text;
}
function getText2(text: number):number {
return text;
}
function getText3(text: boolean): boolean {
return text;
}
getText1('hi'); // 'hi'
getText2(10); // 10
getText3(true); // true
따라서, 앞서 배운 유니온타입으로 함수의 중복 사용을 제거하였다.
function getText1(text:string | number | boolean):string | number | boolean {
return text;
}
getText1('hi'); // 'hi'
getText2(10); // 10
getText3(true); // true
👎 하지만, 유니온타입의 한계가 있다.
✏️ 단점1. 공통으로 접근할 수 있는 속성만 사용 가능하다.
- string, number, boolean이 공통적으로 사용할 수 있는 속성만 사용이 가능하다.
- 만약 number의 속성인 toFixed()를 사용하려하면 string에는 없는 속성이므로 에러가 발생한다.
✏️ 단점2. 반환된 값에 대해서도 공통된 속성만 사용 가능하다
- return 받은값을 저장한 a는 여전히 세가지타입에 대해 정의되어 있고, string의 속성인 split()을 사용하면 number에는 없는 속성이므로 에러가 발생한다.
함수를 정의할때 미리 타입을 정의하는 것이 아니라 어떤 타입을 받을지 공간을 만들어놓는 즉, 제네릭을 작성한다.
그리고 함수호출시에 함수파라미터로 값을 전달하듯이, 호출을 할때 타입을 정의하여 제네릭으로 전달하는 것이다.
아래와 같이 호출시 타입을 string으로 전달하여 함수의 파라미터와 반환값이 string으로 정의 되는 것을 확인 할 수 있다.

💡 잠깐) 그럼 Any 처럼 모든 타입을 받는 것과 같은거 아닌가?
물론 어떤 타입을 정의해야할지 모르거나 여러가지 타입을 허용하고 싶은 경우에는 any를 사용할 수 있다. 하지만, 함수의 파라미터로 어떤 타입이 들어갔고 어떤 값이 반환되는지 확인할 수 없다. 즉, any는 타입을 검사하는 기능을 갖고 있지 않기 때문이다.
따라서, 제네릭을 사용한다. 함수의 이름 뒤에
<T>를 작성하고, 함수의 인자와 반환 값에T를 작성한다. 그리고 호출시 넘길 타입을 받아서 함수의 입력값에 대한 타입과 출력값에 대한 타입이 동일한지 검증할 수 있게 된다.
서비스에는 이메일과 상품개수 총 두개의 드롭다운이 존재한다.
코드 구현으로는 이메일 함수로직과 상품개수 함수로직 하나와 공통된 드롭다운형성로직이 존재한다.
따라서, 드롭다운형성로직에 각각 값들을 삽입하여 출력하게 한다.

이 로직들에 대하여 타입스크립트를 적용하여 각각의 코드에 대한 타입을 정의한다.
이때 createDropdownItem의 item의 경우 emails의 값과 numberOfProducts에 대한 값을 받아온다.
하지만 각각의 value값이 string, number이므로 각각의 인터페이스를 생성후 UnionType을 적용시킨 모습이다.

위와 같은 경우 타입이 다르다는 이유로 중복적인 인터페이스 작성으로 코드가 늘어나게 된다.
따라서, 앞서 배운 제네릭을 이용하여 함수호출시 보낸 타입에 대해 정의한다.
👍 interface에 제네릭을 설정하여 value값에는 string 또는 number가 선택적으로 정의되게 한다.

💡 잠깐) 인터페이스에 제네릭을 설정하는 방법
- object에 대한 타입을 인터페이스로 정의한 경우
interface Dropdown<T> { value: T; selected: boolean; } const obj: Dropdown<number> = {value: 10, selected: false};
- 함수에 대한 인터페이스를 타입으로 정의한 경우
interface GenericLogTextFn { <T>(text: T): T; } function logText<T>(text: T): T { return text; } let myString: GenericLogTextFn = logText;
- 인터페이스에 인자 타입을 강조하고 싶은 경우
interface GenericLogTextFn<T> { (text: T): T; } function logText<T>(text: T): T { return text; } let myString: GenericLogTextFn<string> = logText;
다음과 같은 코드에서는 length를 확인하고 싶지만 Error를 발생시킨다.

왜냐하면 미리 타입이 정의되지 않는 제네릭이 들어있으므로 .length를 사용할 수 있는 타입이 들어온다는 보장이 없다. 제네릭에 어떤 타입이 들어올 줄 모르게 때문이다.
쉽게 말해 .length는 문자열 또는 배열 타입에는 사용되지만 타입으로 number가 정의된다면 사용할 수 없다.
만약, any 타입으로 작성된 함수를 사용했을 경우 프로그램 실행 도중에 문제가 발생할 가능성이 많지만,
이처럼 제네릭 타입을 사용하는 경우 사전에 문제가 되는 부분을 미리 방지할 수 있다는 장점이 있다.
👍 이러한 경우에는 배열 형태의 T를 받아 유연한 방식으로 함수의 타입을 정의할 수 있다.

✅ 단, 주의해야할 점은 배열타입으로 정의한 것과 같기 때문에 배열 형식으로 string을 넣어줘야 한다,


앞서 보았듯이 length와 같은 경우처럼 타입에 대한 제한이 따르는 경우가 있다. 그래서 배열타입을 주는 방법도 있었지만, 또 다른 방법 두가지를 통해서 어느 정도의 타입 힌트를 주어 허용할 수 있다.
✅ 1. extends를 통한 확장
interface LengthType {
length: number;
}
function logTextLength<T extends LengthType>(text: T): T {
text.length;
return text;
}
logTextLength('hello'); // length 사용가능한 타입(string)
logTextLength(10); // length 사용 불가능한 타입(number)
logTextLength({length: 10}) // extend를 통해 타입이 부합하므로 가능
✅ 2. keyof를 통해 제한
extends를 통한 interface의 속성을 상속받기 전까지는 이전의 방법처럼 호출시 타입을 명시하여 어떠한 인자를 삽입해도 상관이 없었다.
getShoppingItemOption<number>(10);
getShoppingItemOption<string>('a');
하지만 extends을 통해 확장하는 순간 shoppingItem의 key인 name, price, stock만 인자로 사용 가능하게 속성을 제한한다.
interface ShoppingItem {
name: string;
price: number;
stock: number;
}
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
return itemOption;
}
getShoppingItemOption('name');
제네릭을 선언할 때 부분에서 첫 번째 인자로 받는 객체에 없는 속성들은 접근할 수 없게끔 제한하였다.
function getProperty<T, O extends keyof T>(obj: T, key: O) {
return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않는다.
.ts 확장자 파일에서는 정상적으로 동작할 수 있는데, .tsx 확장자 파일은 TypeScript + JSX로 구성되어 있어서 에서 태그(<>) 에러가 발생한다.
let GenericReturnFunc = <Type>(arg: Type): Type => {
return arg;
}
따라서 .tsx 확장자 파일에서 제네릭 화살표 함수를 구현해야 하는 경우 제네릭 매개변수에 extneds를 사용하여 컴파일러에게 제네릭 화살표 함수라고 알려줘야 한다.
let GenericReturnFunc = <Type extends {}>(arg: Type): Type => {
return arg;
}
참조 및 참고한 사이트