이처럼 타입스크립트가 추론을 통해 타입을 알아낼 수 있는 경우는 직접 <> 타입을 넣지 않아도 된다.
실제로도 직접 넣지 않는 경우가 더많음
타입스크립트 5.0 버전에서 상수 타입 매개변수 (const T)가 추가됨
상수 타입 매개변수
functionvalues<T>(initial:T[]) {return {hasValue(value:T) { returninitial.includes(value) } }}constsavedValues=values(['a','b','c']); // T는 string[]로 추론됨savedValues.hasValue('x'); // T가 string으로 추론되기에 'x'를 넣어도 타입 오류가 나지 않음
❓T를 string 대신 'a' | 'b' | 'c' 같은 유니온으로 추론되게 하려면?
타입 매개변수 앞에 const 수식어를 추가하면 타입 매개변수 T를 추론할 때 as const를 붙인 값으로 추론됨
// TS v4.9 functionvalues<T>(initial:readonlyT[]) { //readonly 수식어로 정확한 타입을 추론하게끔 설정//...}constsavedValues=values(['a','b','c'] asconst); // as const 접미사로 tuple로 만듬savedValues.hasValue('x'); // T가 'a' | 'b' | 'c' 이므로 에러 발생// TS v5.0functionvalues<constT>(initial:T[]) { // 타입 매개변수 앞에 const 수식어 사용//...}constsavedValues=values(['a','b','c']);savedValues.hasValue('x'); // 타입 에러 발생
제너릭에 제약 걸기
타입 매개변수에는 제약(constraint)를 사용 가능
extends 문법으로 타입 매개변수의 제약을 표시
타입의 상속을 의미하던 extends 와는 사용법이 다르므로 구분!!
제약이 걸리면 제약에 어긋나는 타입은 입력할 수 없지만 제약보다 더 구체적인 타입은 입력 가능
이러한 점에서 제약은 기본값과는 다르다.
interfaceExample<Aextendsnumber,B=string> { a:A, b:B}typeUsecase1=Example<string,boolean>; // 타입 에러typeUsecase2=Example<1,boolean> // number 보다 더 구체적인 타입 입력 가능typeUsecase3=Example<number>// 하나의 타입 매개변수가 다른 타입 매개변수의 제약이 될 수 도 있다.interfaceExample<A,BextendsA> {//...}
‼️ 자주 쓰이는 제약들
<T extends object>// 모든 객체<T extends any[]>// 모든 배열<Textends (...args: any) => any>// 모든 함수<T extends abstract new (...args:any) => any>// 생성자 타입<T extends keyof any>// string | number | symbol
제너릭 제약을 사용할 때 흔히 하는 실수
❌ 타입 매개변수와 제약을 동일하게 생각하는 것
먼저 타입 매개변수가 제약에 대입할 수 있는 타입인지를 따져보아야 한다.
강박적으로 제너릭을 쓸 필요는 없다. 특히, 원시값 타입만 사용한다면 대부분 제약을 걸지 않아도 된다.
interfaceVO { value:any;}// ☠️ 타입 매개변수와 제약을 동일하게 생각해서 발생하는 실수constreturnVO= <TextendsVO>():T=> {return { value:'test' }; // Type Error}// 제너릭을 제거하면됨constreturnVO= ():VO=> {return { value:'test' };}
T 는 정확히 VO가 아니라 VO에 대입할 수 있는 모든 타입을 의미
따라서 { value: string, another: string }도 T가 될 수 있다.
이러면 { value: string } 은 T가 아니다. 따라서 에러가 발생함.
인자값의 타입은 열려있고, 반환값의 타입은 닫혀있다. (공변성, 반공변성 참고)
// ☠️ 타입 매개변수 T 에 boolean 제약이 걸려있고, 함수의 매개변수도 T 타입functiononlyBoolean<Textendsboolean>(arg:T=false):T { // 인자 타입에서 에러 발생return arg;}// onlyBoolean을 유효하게 만들고 싶다면 간단하다. 제너릭을 쓰지 않으면 된다.functiononlyBoolean(arg:boolean=true):boolean {return arg;}
never 타입 때문, never는 모든 타입에 대입할 수 있으므로 never extends boolean은 참이다.