typeChooseArray<A> =Aextendsstring?string[] :never;typeStringArray=ChooseArray<string>; // type StringARray = string[]typeNever=ChoosArray<number>; // type Never = never;
never 타입은 모든 타입에 대입할 수 있기에 모든 타입을 extends 할 수 있다.
typeResult=neverextendsstring?true:false; // type Result = true
매핑된 객체 타입에서 키가 never 이면 해당 속성은 제거된다.
typeOmitByType<O,T> = { [KinkeyofOasO[K] extendsT?never:K]:O[K];}typeResult=OmitByType<{ // type Result = { name: string, age: number } name:string; age:number; married:boolean; rich:boolean;},boolean>
중첩 삼항연산자
컨디셔널 타입은 자바스크립트 삼항연산자처럼 중첩해서 만들기 가능
typeChooseArray<A> =Aextendsstring?string[]:Aextendsboolean?boolean[] :never;typeStringArray=ChooseArray<string>; // string[]typeBooleanArray=ChooseArray<boolean>; // boolean[]typeNever=ChooseArray<number>; // never
인덱스 접근 타입으로 컨디셔널 타입을 표현 가능
typeA1=string;// B1 타입과 B2 타입은 같음typeB1=A1extendsstring?number:boolean;// B2 처럼 왜 굳이 복잡하게 사용하냐면, 참일 때와 거짓일 때의 타입이 복잡한 경우는 아래처럼 나타내기도 함typeB2= {'t':number;'f':boolean;}[A1extendsstring?'t':'f']
분배 법칙
제너릭과 never의 조합은 더 복잡한 상황에서 진가를 발휘함
string | number 타입으로부터 string[] 타입을 얻고 싶을 때?
typeStart=string|number;// string | number 가 string을 extends 할 수 없기 때문// string이 더 구체적이므로 대입 불가능 typeResult=Startextendsstring?Start[] :never; // never// 컨디셔널 타입을 제너릭과 함께 사용하면 원하는 동작 가능typeStart=string|number;typeResult<Key> =Keyextendsstring?Key[] :never;let n:Result<Start> = ['hi']; // let n: string[]
검사하려는 타입이 제너릭이면서 유니언이면 분배법칙이 실행됨
Result<string | number>는 Result<string> | Result<number> 가 된다.
따라서 Key extends string | boolean ? Key[] : never를 거치면서 string [] | never가 되고 never는 사라져서 최종적으로 string[] 타입이 된 것
typeIsString<T> =Textendsstring?true:false;// 분배법칙에 의해 IsString<'h1'> | IsString<3> -> true | false => booleantypeResult=IsString<'h1'|3>; // type Result = boolean// 배열로 감싼 경우typeIsString<T> = [T] extends [string] ?true:false;typeResult=IsString<'h1'|3>; // type Result = false
never도 분배법칙의 대상이 된다.
never가 유니언으로 보이지는 않지만 유니언으로 생각하는것이 좋다.
never가 분배법칙이 일어나면 never가 된다. (공집합에서 분배법칙이 일어나면 아무것도 실행하지 않기때문)
typeR<T> =Textendsstring?true:false;// 분배법칙이 일어나서 true가 아니라 never가 됨typeRR=R<never>; // type RR = never
never는 공집합과 같으므로공집합에서 분배법칙을 실행하는 것은 아무것도 실행하지 않는것과 같다.
간단하게 제너릭과 never가 만나면 never가 된다고 생각하자.
따라서 never를 타입인수로 사용하려면 분배법칙이 일어나는것을 막아야 한다.
typeIsNever<T> = [T] extends [never] ?true:false;typeT=isNever<never>; // Type T = truetypeF=isNever<'never'>; // Type F = false
같은 이유로 제너릭과 컨디셔널 타입을 같이 사용할 때는 다음을 조심하자.
타입스크립트는 제너릭이 들어있는 컨디셔널 타입을 판단할 때 값의 판단을 뒤로 미룬다.
이 때도 타입스크립트가 판단을 뒤로 미루지 못하도록 배열로 제너릭을 감싸면 된다.
functiontest<T>(a:T) {typeR<T> =Textendsstring?T:T; // R<T> 타입이 T 타입이 될거라고 생각하는게 잘못됨// 즉, 변수 b에 매개변수 a를 대입할 때까지도 타입스크립트는 R<T>가 T 라는것을 알지 못한다.constb:R<T> = a; // type 'T' is not assignable to type 'R<T>'}// 제너릭을 배열로 감싸면 타입 판단을 뒤로 미루지 않게됨// 타입 매개변수를 선언할 때 바로 <[T] extends [string]> 하는것이 불가능하므로// 한 번 더 컨디셔널 타입으로 묶어서 선언한 것functiontest<Textends ([T] extends [string] ?string:never)>(a:T) {typeR<T> = [T] extends [string] ?T:T;constb:R<T> = a;}