일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- HTTP 완벽 가이드
- 쉬운 문제
- Nestjs
- BFS
- 가천대
- 크롤링
- type challenge
- Crawling
- 백준
- 프로그래머스 레벨 2
- 프로그래머스
- javascript
- 타입 챌린지
- 문자열
- 소켓
- 타입스크립트
- HTTP
- typescript
- Algorithm
- 수학
- dfs
- 그래프
- socket
- TCP
- Node.js
- dp
- 자바스크립트
- 레벨 1
- ip
- 알고리즘
- Today
- Total
kakasoo
new (…args): T 타입이란? 본문
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
NestJS Swagger Library에는 위와 같은 interface가 정의되어 있다.
이 타입은 신기하게도 내부에 new 키워드를 사용하고 있으며, 전개연산자와 제너릭을 모두 쓰고 있다.
이 인터페이스를 어떻게 해석하면 좋을까?
단계를 나눠서 차례대로 설명하면 아래처럼 표현할 수 있다.
// NOTE 1
interface MyType extends Function {
keyName: 'kakasooFunction';
}
임시로 MyType이라는 interface를 만들었다.
이 인터페이스는 Function 이라는 인터페이스를 확장하여 keyName이라는 프로퍼티를 가지고 있다.
/**
* Creates a new function.
*/
interface Function {
/**
* Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function.
* @param thisArg The object to be used as the this object.
* @param argArray A set of arguments to be passed to the function.
*/
apply(this: Function, thisArg: any, argArray?: any): any;
/**
* Calls a method of an object, substituting another object for the current object.
* @param thisArg The object to be used as the current object.
* @param argArray A list of arguments to be passed to the method.
*/
call(this: Function, thisArg: any, ...argArray: any[]): any;
/**
* For a given function, creates a bound function that has the same body as the original function.
* The this object of the bound function is associated with the specified object, and has the specified initial parameters.
* @param thisArg An object to which the this keyword can refer inside the new function.
* @param argArray A list of arguments to be passed to the new function.
*/
bind(this: Function, thisArg: any, ...argArray: any[]): any;
/** Returns a string representation of a function. */
toString(): string;
prototype: any;
readonly length: number;
// Non-standard extensions
arguments: any;
caller: Function;
}
따라서 일반적인 함수에 keyName이라는 키 이름으로 ‘kakasooFunction’이라는 string을 대입해주면 된다.
// NOTE 1
interface MyType extends Function {
keyName: 'kakasooFunction';
}
const someFunction = () => {};
someFunction.keyName = 'kakasooFunction' as const;
const func: MyType = someFunction;
이렇게 작성된 코드에는 빨간색 밑줄이 나오지 않는다.
someFunction은 함수의 형태로 만들어졌으며, keyName에 정확히 일치하는 문자열이 들어갔기 때문이다.
이 첫번째 단계로, Function은 인터페이스이며, 인터페이스를 확장할 수 있고, 타입으로 쓴다는 것을 알 수 있다.
// NOTE 2
interface MyType2 extends Function {
keyName: 'kakasooFunction';
innerFunction: () => number;
}
const someFunction2 = () => {};
someFunction2.keyName = 'kakasooFunction' as const;
someFunction2.innerFunction = () => 3; // New Line!
const func2: MyType2 = someFunction2;
두번째 단계는, innerFunction이라는 키 이름이 추가되었고, 이 키 값은 number를 리턴하는 함수 타입이다.
따라서 keyName을 할당하던 것과 마찬가지로 함수를 하나 대입해주면 func2에도 빨간줄이 나오지 않는다.
두번째 단계에서는, 객체 내부 프로퍼티의 타입을 함수로도 정할 수 있다는 것을 알았다.
// NOTE 3
interface MyType3<T = any> extends Function {
keyName: 'kakasooFunction';
innerFunction: () => T;
}
const someFunction3 = () => {};
someFunction3.keyName = 'kakasooFunction' as const;
someFunction3.innerFunction = () => 3;
const func3: MyType3<number> = someFunction3;
세번째 단계에서는 제너릭
이 추가되었다.
MyType3는 any를 default type으로 하는 T generic type
을 받아, 내부에서 innerFunction의 반환형으로 정의했다.
따라서 아래와 같이 MyType3<number> 라고 타입을 지정한 순간 innerFunction은 number를 리턴하게끔 강제
된다.
이제 제너릭으로 받은 타입을, 확장하고자 하는 인터페이스 내부에서 쓸 수 있단 것도 알게 됐다.
// NOTE 4
interface MyType4<T = any> extends Function {
keyName: 'kakasooFunction';
(): T;
}
이제 인터페이스 내부에서 프로퍼티 이름이었던 innerFunction을 지워버렸다.
프로퍼티 이름으로 innerFunction이 있고, 그게 T 타입을 반환해야 한다는 의미였을 때,
someFunction3.innerFunction = () => 3;
이처럼 innerFunction에 대입한 값은 someFunction3.innerFunction()
의 형식으로 호출할 수 있었다.
이제 여기서 프로퍼티 이름이었던 innerFunction을 지운 타입을 명시했으니,
이건 someFunction4()
의 형식으로 바로 호출할 수 있어야 한다는 의미가 된다.
const someFunction4 = () => 3;
someFunction4.keyName = 'kakasooFunction' as const;
const func4: MyType4<number> = someFunction4;
따라서 이번의 func4가 가진 MyType4는 확장한 함수 인터페이스에 대한 설명이나 마찬가지다.
이제 대망의 5번째 단계다.
// NOTE 5
interface MyType5<T> extends Function {
new (): T;
}
이 단계에서는 이름 앞에 new가 붙은, 딱 3글자만이 바뀌었다.
앞서 확장한 Function interface에 대한 설명으로, 그 자체로도 호출할 수 있어야 한다고 했는데,
이제 new가 붙은, 그 자체로도 호출할 수 있는 함수
가 되었으니 이런 함수는 딱 하나 밖에 없다.
바로 클래스의 생성자 함수
다.
// NOTE 5
interface MyType5<T> extends Function {
new (): T;
}
class SomeClass {
constructor() {}
}
type SomeClassConstructorType = MyType5<SomeClass>;
const SomeClassConstructor: SomeClassConstructorType = SomeClass;
MyType이 클래스의 생성자 함수라는 것을 알았기 때문에 이제 우리는 이러한 식들도 성립한다는 걸 알 수 있다.
먼저 MyType<SomeClass>는 SomeClass의 생성자 함수를 의미하는 함수 타입
이라는 것알 알 수 있다.
그 타입을 SomeClassContructorType 이라는 직관적인 이름으로 저장해두었다.
그리고 someClasConstructor 라는 변수 명에 대한 타입으로 지정하고, SomeClass를 값으로 대입
할 수 있다.
왜냐하면 생성자 함수는 곧 클래스와 동일하기 때문이다.
class Person {
constructor(name: string) {}
}
예를 들어 위와 같이 Person 클래스가 있다고 해보자.
Person 클래스의 생성자 함수 타입은 new (name:string) ⇒ Person
이라고 정의할 수 있다.
이는 Person이라는 클래스가, new를 앞에 붙이고 이름을 받아 인스턴스를 생성하는 함수라는 점에서,
아래와 같은 표현도 가능하게 한다.
type PersonType = Person;
type PersonConstructorType = new (name: string) => Person;
const person: PersonType = new Person('John'); // OK
const personConstructor: PersonConstructorType = Person;
const anotherPerson: PersonType = new personConstructor('Jane'); // OK
다시 처음으로 돌아가, 맨 처음 이 논의의 시작이었던 Type interface를 보자.
export interface Type<T = any> extends Function {
new (...args: any[]): T;
}
이는 Type<T = any>, 즉 어떤 클래스 T에 대한 생성자 함수를 의미한다.
이 생성자 함수는 파라미터가 몇 개인지는 아직 알 수 없고, 어쨌든 T를 생성해준다는 것만 이해할 수 있다.
import { Type } from '@nestjs/common';
import { ExamplesObject, ReferenceObject, RequestBodyObject, SchemaObject } from '../interfaces/open-api-spec.interface';
import { SwaggerEnumType } from '../types/swagger-enum.type';
declare type RequestBodyOptions = Omit<RequestBodyObject, 'content'>;
interface ApiBodyMetadata extends RequestBodyOptions {
type?: Type<unknown> | Function | [Function] | string;
isArray?: boolean;
enum?: SwaggerEnumType;
}
interface ApiBodySchemaHost extends RequestBodyOptions {
schema: SchemaObject | ReferenceObject;
examples?: ExamplesObject;
}
export declare type ApiBodyOptions = ApiBodyMetadata | ApiBodySchemaHost;
export declare function ApiBody(options: ApiBodyOptions): MethodDecorator;
export {};
말했다시피 이건 Swagger 라이브러리 때문에 시작된 이야기였고, 여기서 Type 사용 예시를 볼 수 있다.
type은 Type 이거나 Function, [Function], string을 타입으로 가진다.
따라서 ApiBody.options.schema의 type은,
만약 type이 존재한다면 클래스 (DTO), Wrapper Function, Wrapper Function의 튜플 형태와 string,
이 중 한 가지를 타입으로 받는다는 것으로 해석될 수 있다.
'프로그래밍 > TypeScript' 카테고리의 다른 글
NTuple 타입 정의하기 (0) | 2023.01.21 |
---|---|
type Push = <T extends any[], value> = […T, value] (0) | 2023.01.20 |
배열의 Length를 뽑는 타입 (0) | 2023.01.20 |
Extract<Type, Union> (0) | 2023.01.18 |
싱글턴(Singleton) 패턴이란? (0) | 2023.01.13 |