Today I Learned_230420
TypeScript
- Parameters
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
- <T extends (…args: any) => any> : T는 무조건 함수여야 한다.
- infer
- 타입스크립트가 알아서 추론하는 것
- extends에서만 사용 가능
- 추론 조건 ? 추론 성공 시의 값 : 추론 실패 시의 값
- 타입스크립트가 매개변수 자리를 추론하도록
- T extends (…args: infer P) => any ? P : never;
- T extends (…args: infer P) 추론한 값이 있으면
- P를 써라
- 없으면 쓰지 마라(never)
function zip(x: number, y:string, z:boolean): {x:number, y:string, z:boolean} {
return {x, y, z};
}
type Params = Parameters<typeof zip>;
-
Parameter는 zip의 매개변수들을 받음
-
ReturnType
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
- Parameter에서 infer 위치가 바뀐 것
- 아래와 같이 사용
function zip(x: number, y:string, z:boolean): {x:number, y:string, z:boolean} {
return {x, y, z};
}
type R<T extends (...args: any) => any> = T extends (...args: any) => infer A ? A : never;
type Ret = R<typeof zip>;
- ConstructorParameters
- 생성자 대상으로 사용
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
class A {
constructor(a: string, b: number, c: boolean) {
this.a = a;
this.b = b;
this.c = c;
}
a: string;
b: number;
c: boolean;
}
const c = new A('123', 456, true);
type C = ConstructorParameters<typeof A>; // [string, number, boolean]ㅋ
- 클래스(A)는 바로 타입으로 쓸 수 있음
-
엄밀하게 말하자면 인스턴스(new 붙여서 만들어낸것)에 해당
- InstanceType
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
class A {
constructor(a: string, b: number, c: boolean) {
this.a = a;
this.b = b;
this.c = c;
}
a: string;
b: number;
c: boolean;
}
type I = InstanceType<typeof A>
- 기타
/**
* Convert string literal type to uppercase
*/
// 타입스크립트 코드로는 구현이 되지 않아서 따로 처리했다는 뜻
type Uppercase<S extends string> = intrinsic;
/**
* Convert string literal type to lowercase
*/
type Lowercase<S extends string> = intrinsic;
/**
* Convert first character of string literal type to uppercase
*/
type Capitalize<S extends string> = intrinsic;
/**
* Convert first character of string literal type to lowercase
*/
type Uncapitalize<S extends string> = intrinsic;
// intrinsic은 이렇게 코드로 구현되어 있음.
// 코드로는 구현이 되어도 타입만으로는 구현이 어렵다.
function applyStringMapping(symbol: Symbol, str: string) {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
}
return str;
}
/**
* Marker for contextual 'this' type
*/
// 구현부가 타입스크립트로는 안 되어있음
// this의 타입을 가져옴
interface ThisType<T> { }
- Promise, Await
const p1 = Promise.resolve(1).then((a) => a+1).then((a) => a.toString());
// Promise<number>, Promise<number>, Promise<number>, Promise<string>
const p2 = Promise.resolve(2);
const p3 = new Promise((res, rej) => { // value를 넣지 않고 resolve하는 것 -> Promise<unknown>
setTimeout(res, 1000);
});
// Promise.all -> p1, p2, p3 프라미스를 한꺼번에 실행하고 그 결과를 돌려주는 것
// then에 마우스를 대 보면 [string, number, unknown]으로 타입을 정확하게 추론하는 것을 알 수 있다
// 어떻게 p1의 타입을 알지?!
Promise.all([p1, p2, p3]).then((result) => {
// Awaited 분석 필요
// {'0': string, '1': number, '2':unknown, length: 3}
console.log(result); // ['3', 2, undefined]
})
// Awaited를 사용하면 재귀적인 Promise를 풀 수가 있음
type Result = Awaited<Promise<Promise<Promise<number>>>>;
- Await
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T; // non-object or non-thenable
- bind
bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
bind<T, A0, A1, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1): (...args: A) => R;
bind<T, A0, A1, A2, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2): (...args: A) => R;
bind<T, A0, A1, A2, A3, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): (...args: A) => R;
bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
- ThisParameterType과 OmitThisParameter가 나온다.
- 첫번째 줄 → this가 없는 원래 함수가 나온다.
-
함수형 프로그래밍 할 때 중요하긴 하지만, 타입적 한계가 있음(완벽하지 않음)
- bind 사용 예제 (왜이렇게 오버로딩이 많이 되어있는지에 대한 해답)
// bind를 통해 this 바꾸기
const heejung = {
name: 'heejung',
sayHello() {
console.log(`hi ${this.name}`);
}
}
const sayHello = heejung.sayHello;
const sayHi = heejung.sayHello.bind({name: 'juyoung'}); // 이렇게 bind를 통해 this를 바꿀 수 있다.
sayHi();
function add(a: number, b:number, c:number, d:number, e:number, f:number) {
return a+b+c+d+e+f;
}
const add1 = add.bind(null); // this를 사용하지 않고 있기에 똑같은 함수가 나온다.
add1(1, 2, 3, 4, 5, 6);
// bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
const add2 = add.bind(null, 1); // 매개변수 자리에 미리 값을 가져다 놓는다 - 이걸 바인딩(매개변수 고정)
add2(2, 3, 4, 5, 6);
const add3 = add.bind(null, 1, 2);
add3(3, 4, 5, 6);
const add4 = add.bind(null, 1, 2, 3);
add4(4, 5, 6);
const add5 = add.bind(null, 1, 2, 3, 4); // const add5: (e: number, f: number) => number
add5(5, 6);
// 5개 이상 bind에는 지원이 되지 않는다.
const add6 = add.bind(null, 1, 2, 3, 4, 5); // const add6: (...args: 1 | 2 | 3 | 4 | 5[]) => number -> 왜이러지?
add6( 6);
- ThisParameterType
type ThisParameterType<T> = T extends (this: infer U, ...args: never) => any ? U : unknown;ㅇㅇㅇㅇㅇㅅㅅㅅㅅ
- T extends (this: infer U, …args: never) 로 보아 T는 함수임을 알 수 있다.
- this: infer U → this를 추론해 본다.
-
추론에 성공하면 기존 타입을 쓰는 거고, 실패하면 unknown을 리턴한다.
- OmitThisParameter
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
- ThisParameterType이 unknown일 때는 함수가 나온다.
- ThisParameterType이 unknown이 되는 경우는 타입 추론을 실패했을 경우
- 타입 추론이 실패했을 때는 함수 타입 그대로 반환.
- 타입 추론이 성공했을 때는 그 뒤 진행 (매개변수와 리턴값 알아내서 그걸 바탕으로 함수를 만들어내라)
- 뒤에 보면 매개변수에 this가 없다 → this를 제외한 매개변수를 가져온다.
- 어느 경우나 this는 없음 → OmitThisParameter this를 없애버리는 함수다.