즉, 여러 타입을 받아들임으로써 여러 형태를 가지는 것을 의미
interface SuperPrint {
(arr: number[]): void;
(arr: boolean[]): void;
(arr: string[]): void;
(arr: number[] | boolean[]): void;
}
const superPrint: SuperPrint = (arr) => {
arr.forEach((elem) => console.log(elem));
};
superPrint([1, 2, 3]);
superPrint([true, false, false]);
superPrint(["a", "b"]);
superPrint([1, 2, true, false]);
이 코드는 배열을 출력해주는 메서드를 구성했는데 문제가 있음.
매번 새로운 타입을 받고 싶을 때마다 새로운 타입의 배열을 받을 수 있다고 명시하기엔 비효율적
아무 타입이나 들어올 수 있게 하는 것도 비효율적
그렇다면 답은 제네릭을 이용해보는 것이다.
interface SuperPrint<T> {
(arr: T[]): void
}
const superPrint: SuperPrint<number> = (arr) => {
arr.forEach((el) => console.log(el));
}
superPring([1,2,3]);
함수 타입을 정의할 때, 어떤 배열을 처리할 것인지만 뒤에 붙여주면 된다.
interface SuperPrint {
<T>(arr: T[]): void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach((el) => console.log(el));
};
superPrint([1,2,3,"4"]);
이와 같이 작성하면 제네릭의 타입을 추론해서 타입스크립트가 적용한다.

그렇지만 이 방법은 타입 추론에 기대는 방식이기에 선호하지 않고, 사실상 아무 배열이나 튜플도 들어갈 수 있기 때문에 제약이 너무 적어짐
즉, any와의 차이가 무엇일지 잘 생각해봐야 함
제네릭 = 요구한 대로 signature 를 생성해줄 수 있는 도구
라이브러리를 만들거나, 다른 개발자가 사용할 기능을 개발하는 경우엔 제네릭이 유용할 수 있음
그 외의 경우에는 제네릭을 직접 작성할 일은 없고
사용만 하게 될 것임
interface Person<E> {
name: string;
extraInfo: E;
}
interface PersonDetail extends Person<string> {}
const personDetail: PersonDetail = {
name: "HWI",
extraInfo: "male, developer",
};
type Man<E> = {
name: string;
extraInfo: E;
};
type ManDetail = Man<string>;
const manDetail: ManDetail = {
name: "HWI",
extraInfo: "developer, South Korea.",
};
위 코드와 같이 유연하게 상속(extends) 시에 사용할 수도 있다.
class Person {
name;
// 생성자 함수
constructor(name: string) {
this.name = name;
}
}
// Person obj를 a에 할당
const a = new Person("Kim");
console.log(a);
// class es6부터 추가.
// es5에서 사용할 수 있도록 하기 위해
// example.js 에서 class => function으로 돼있음
// class 이름은 대문자로 시작.
class Person {
// tsconfig.json : "strict" = true.
// "strictNullChecks": true,
// "strictPropertyInitialization": true, => age:number에 값이 할당된 게 없음(undefined)
// 초기값을 설정해주지 않으면 오류발생.
name: string = "Mark";
age: number;
// age!: number => 초기값을 지금 설정안하고 나중에 설정할 때 사용(주의)
// async constructor (x)
constructor(age?:number) {
if (age === undefined) {
this.age = 20;
} else {
this.age = age;
}
}
// JS에서는 오버로딩이 안됨. TS에서는 가능.
}
const p1: Person = new Person(39);
// Perons이 async(비동기)라면 await p1.init();
const p2: Person = new Person(); // 이런 형태 두개 다 지원하려고 한다면 각각의 생성자 생성(오버로딩)
console.log(p1);
// p1.age = 39;
console.log(p1.age);
// 접근제어자 public, private
class Person {
public name: string = "Mark";
// 과거에는 private가 없어서 _age 이런 식으로 이름에 표기를 해둠
// 아직도 관행이 남아서 private는 _를 붙여줌.
private age: number;
public constructor(age?:number) {
if (age === undefined) {
this.age = 20;
} else {
this.age = age;
}
}
public async init() {
}
}
const p1: Person = new Person(39);
console.log(p1);
// 파라미터에 public을 붙여줌.
class Person {
public constructor(public name: string, private age: number) { // age는 외부에서 접근 불가.
}
}
const p1: Person = new Person("Mark", 39);
console.log(p1);
class Person {
public constructor(private _name: string, private age: number) {
}
// 얻어오기.
get name() {
// return 필수.
// console.log('get');
return this._name + "Lee";
}
// 변경하기.
set name(n:string) {
// 인자 필수
// console.log('set');
// count해서 몇번 set 되었는지 활용할 수 도 있음.
this._name = n;
}
}
const p1: Person = new Person("Mark", 39);
console.log(p1.name); // Get, get을 하는 함수 : getter
p1.name = 'Woongjae'; // Set, set을 하는 함수 : setter
console.log(p1.name);
class Person {
public readonly name: string = 'Mark';
private readonly country: string;
public constructor(private _name: string, private age: number) {
// constructor 안에서만 this로 값 세팅 가능.
this.country = "Korea";
}
hello() {
// readonly이고 private이므로 수정 불가.
// this.country = "China";
}
}
const p1: Person = new Person("Mark", 39);
console.log(p1.name); // Get, get을 하는 함수 : getter
// p1.name = 'Woongjae'; // Set, set을 하는 함수 : setter
console.log(p1.name);