
함수에 매번 다른 타입의 매개변수가 들어오면 계속 매개변수에 들어올 타입을 추가해줘야한다...
//string
function getArrayLength(arr: number[]): number {
return arr.length;
}
const array1 = [1, 2, 3];
getArrayLength(array1);
//string & number
function getArrayLength(arr: number[] | string[]): number {
return arr.length;
}
const array2 = ['a', 'b', 'c'];
getArrayLength(array2);
//string & number & boolean
function getArrayLength(arr: number[] | string[] | boolean[]): number {
return arr.length;
}
const array3 = [true, false, true];
getArrayLength(array3);
이때, 제네릭을 사용할 수 있다.
제네릭은 타입을 함수의 파라미터처럼 사용할 수 있다.
function getArrayLength<T>(arr: T[]): number {
return arr.length;
}
<T> 는 Type이라는 뜻, 아무 단어나 사용해도 무관하지만 관습적으로 T 라고 작성함.
함수 이름 뒤에 붙여 쓰고, 매개변수의 타입에도 붙여준다.
const array1 = [1, 2, 3];
getArrayLength<number>(array1);
const array2 = ['a', 'b', 'c'];
getArrayLength<string>(array2);
const array3 = [true, false, true];
getArrayLength<boolean>(array3);
함수를 호출할 때 타입을 쓰면 해당 타입이 매개변수에 자동으로 적용되는 원리? 라고 볼 수 있다.
interface Vehicle {
name: string;
color: string;
option: any; //어떤 타입이든 올 수 있음
}
const car: Vehicle = {
name: 'Car',
color: 'red',
option: {
price: 1000
}
}
const bike: Vehicle = {
name: 'Bike',
color: 'green',
option: true
}
interface Vehicle<T> {
name: string;
color: string;
option: T
}
const car: Vehicle<{price : number}> = {
name: 'Car',
color: 'red',
option: {
price: 1000
}
}
const bike: Vehicle<boolean> = {
name: 'Bike',
color: 'green',
option: true
}
특정한 타입을 지정하지 않는다는 점에서는 any와 비슷하게 느낄 수 있지만
또, 제네릭은 재사용성이 높은 함수와 클래스를 생성할 수 있다는 장점이 있다.
const makeArrN = (x: number) => {
return [x]
}
const array1 = makeArrN(5);
const array2 = makeArrN('a'); //ERROR
const makeArrG = <T>(x: T) => {
return [x]
}
const array3 = makeArrG(5);
const array4 = makeArrG('a');
제네릭을 사용하면 여러 타입에 대해 일일이 타입을 지정할 필요가 없으므로 편하다.
const makeArr = <T, Y>(x: T, y: Y) => {
return [x, y]
}
const array1 = makeArr(5, 6); //[5, 6]
const array2 = makeArr('a', 'b'); //['a', 'b']
const array3 = makeArr(4, 'a'); //[4, 'a']
이렇게만 작성해도 타입스크립트가 똑똑해서 리턴값의 타입을 적절히 추론해서 타입 에러가 발생하진 않지만, 정확하게 해주기 위해서 반환값에도 타입을 지정해주면 좋겠져?
아래 두 코드는 동일하게 작동한다.
//1
const makeArr = <T, Y>(x: T, y: Y): [T, Y] => {
return [x, y]
}
const array3 = makeArr<number, string>(4, 'b');
//2
const makeArr = <T, Y = string>(x: T, y: Y): [T, Y] => {
return [x, y]
}
const array3 = makeArr<number>(4, 'b');
const makeFullName = (obj: {firstName : string, lastName : string}) => {
return {
...obj,
fullName: obj.firstName + ' ' + obj.lastName
}
}
const user1 = makeFullName({firstName : 'John', lastName : 'Doe'});
만약 user1 객체에 location 이라는 속성을 추가하고 싶다면?
➡️ location 속성에 대한 타입도 정의해줘야할텐데 그러면 makeFullName() 함수를 사용해서 만드는 모든 객체가 location 속성을 가져야 한다.
근데 나는 user1 객체만 location 속성을 가지면 좋겠어. 그러면 어떻게 해야될까?
모든 객체가 firstName, lastName 속성을 필수로 갖게 하고 나머지는 필요에 따라 추가하게 만들면 되지 않을까?
const makeFullName = <T extends {firstName : string, lastName : string}>(obj: T) => {
return {
...obj,
fullName: obj.firstName + ' ' + obj.lastName
}
}
const user1 = makeFullName({firstName : 'John', lastName : 'Doe', location : 'Seoul'});
extends 키워드를 사용해서 obj가 갖는 제네릭 타입이 필수로 {firstName : string, lastName : string} 를 갖도록 하면 된다.