Today I Learned_230420

5 분 소요

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를 없애버리는 함수다.

카테고리:

업데이트: