...
타입스크립트 - Utility Types
지금까지 타입스크립트를 다루면서, 자바스크립트를 어느정도 아니까 타입 종류만 배우면 뚝딱 마스터 할 줄 알았더니, 타입 자체를 코딩하며 에러줄을 사라지게 하는 이른바 타입을 코딩 하고 있는 자신을 발견했을 것이다. 🤣
이러한 고충을 타입스크립트 개발진들이 알았는지, TypeScript는 공통 타입 변환을 용이하게 하기 위해 몇 가지 유틸리티 타입을 제공한다.
예를 들자면 자바스크립트에서 배열을 다루는데 있어 for , while문 이면 충분하겠지만,
따로, forEach문 이나 배열을 만드는 map, 배열요소를 찾는 find나 filter 같은, 배열을 다루는데 편리하게 이용가능한 루프 헬퍼 함수를 지원하듯이, 타입스크립트에서도 타입 변경을 쉽고 용이하게 하기위해 헬퍼 함수처럼 Utility Type을 제공한다고 보면 된다.
유틸리티 타입을 공부하지 않아 쓰지 않더라도 기존의 인터페이스, 제네릭 등의 기본 문법으로 충분히 타입을 변환할 수 있지만 유틸리티 타입을 쓰면 훨씬 더 간결한 문법으로 타입을 정의할 수 있으며, 특히 이미 정의해 놓은 타입을 변환할 때 유용하게 쓰인다.
사실 유틸리티 타입은 어떤 독립적인 코드 문법이 아니다.
모두 우리가 배운 타입스크립트 문법(맵드 타입과 조건부 타입)을 이용해 유저들이 사용하기 편하라고 짜집기 한 함수이다.
남이 만들어 놓은 코드만 잘 갖다 쓰면 된다고 생각하지만, 만일 약간 변형된 타입 형태를 얻고 싶다면 반드시 직접 커스텀 해야 하는 때가 온다. 따라서 유틸리티 타입의 타입 문법 구성을 보며 어떤 문법 원칙에 의해 특정 타입의 모양을 반환하는지 연구할 필요가 있다.
우선 어떤 유틸리티 타입이 있는지 둘러보며, 왜 이 문법 구성이 이러한 타입을 반환하는지에 대한 공부도 병행하길 바란다.
유틸리티 타입 문법에 사용되는 맵드 타입(Mapped Types) 문법 & 조건부 타입(Conditional Types) 원리에 대한 포스팅은 다음 글에 잘 설명 되어 있으니 참고하길 권한다.
유틸리티 타입 | 설명 (대표 타입) | 타입 인자 |
Partial | TYPE의 모든 속성을 선택적으로 변경한 새로운 타입 반환 (인터페이스) | <TYPE> |
Required | TYPE의 모든 속성을 필수로 변경한 새로운 타입 반환 (인터페이스) | <TYPE> |
Readonly | TYPE의 모든 속성을 읽기 전용으로 변경한 새로운 타입 반환 (인터페이스) | <TYPE> |
Record | KEY를 속성으로, TYPE를 그 속성값의 타입으로 지정하는 새로운 타입 반환 (인터페이스) | <KEY, TYPE> |
Pick | TYPE에서 KEY로 속성을 선택한 새로운 타입 반환 (인터페이스) | <TYPE, KEY> |
Omit | TYPE에서 KEY로 속성을 생략하고 나머지를 선택한 새로운 타입 반환 (인터페이스) | <TYPE, KEY> |
Exclude | TYPE1에서 TYPE2를 제외한 새로운 타입 반환 (유니언) | <TYPE1, TYPE2> |
Extract | TYPE1에서 TYPE2를 추출한 새로운 타입 반환 (유니언) | <TYPE1, TYPE2> |
NonNullable | TYPE에서 null과 undefined를 제외한 새로운 타입 반환 (유니언) | <TYPE> |
Parameters | TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (함수, 튜플) | <TYPE> |
ConstructorParameters | TYPE의 매개변수 타입을 새로운 튜플 타입으로 반환 (클래스, 튜플) | <TYPE> |
ReturnType | TYPE의 반환 타입을 새로운 타입으로 반환 (함수) | <TYPE> |
InstanceType | TYPE의 인스턴스 타입을 반환 (클래스) | <TYPE> |
ThisParameterType | TYPE의 명시적 this 매개변수 타입을 새로운 타입으로 반환 (함수) | <TYPE> |
OmitThisParameter | TYPE의 명시적 this 매개변수를 제거한 새로운 타입을 반환 (함수) | <TYPE> |
ThisType | TYPE의 this 컨텍스트(Context)를 명시, 별도 반환 없음! (인터페이스) | <TYPE> |
Partial<T>
- TYPE의 모든 속성을 선택적(?)으로 변경한 새로운 타입을 반환
모든 속성 타입을 옵셔널으로 만드는건 좋지않기 때문에 PICK 이나 OMIT 유틸리티 타입을 더 활용하는 편이다.
type Partial<T> = {
[P in keyof T]?: T[P];
};
Partial<TYPE>
interface User {
name: string;
age: number;
phone: number;
}
type Partial_User = Partial<User> // 인터페이스 User의 속성을 모두 optional 설정
/*
type Partial_User = {
name?: string | undefined;
age?: number | undefined;
phone?: number | undefined;
}
*/
const user: Partial<User> = {
name: 'B',
};
Required<T>
- TYPE의 모든 속성을 옵셔널에서 필수(required)로 변경한 새로운 타입을 반환
type Required<T> = {
[P in keyof T]-?: T[P];
};
Required<TYPE>
interface User {
name?: string;
age?: number;
phone: number;
}
type Required_User = Required<User>; // 인터페이스 User의 속성을 모두 일반 타입으로
/*
type Required_User = {
name: string;
age: number;
phone: number;
}
*/
const user: Required<User> = {
name: '홍길동',
age: 22,
phone: 111,
};
Readonly<T>
- TYPE의 모든 속성을 읽기 전용(readonly)으로 변경한 새로운 타입을 반환
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Readonly<TYPE>
interface User {
name?: string;
age?: number;
phone: number;
}
type Readonly_User = Readonly<User>; // 인터페이스 User의 속성을 모두 readonly 설정
/*
type Required_User = {
readonly name?: string | undefined;
readonly age?: number | undefined;
readonly phone: number;
}
*/
const user: Readonly<User> = {
name: '홍길동',
age: 22,
phone: 111,
};
user.age = 11; // ERROR !!
Record<KEY, TYPE>
- 제네릭의 KEY를 속성으로, 제네릭의 TYPE를 속성값의 타입으로 지정하는 새로운 타입을 반환
{ KEY: TYPE, ... }형태로 반환- 이 유틸리티는 타입의 프로퍼티들을 다른 타입에 매핑시키는 데 사용
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Record<KEY, TYPE>
type Key = 'name' | 'age' | 'phone';
type Record_User = Record<Key, number>; // 유니온 Key의 'name', 'age', 'phone' 타입들을 속성으로 하여 number 타입으로 설정
/*
type Record_User = {
name: number;
age: number;
phone: number;
}
*/
const user: Record<Key, number> = {
name: 9999,
age: 22,
phone: 111,
};
Pick<TYPE, KEY>
- 제네릭 TYPE으로 부터 제네릭 KEY에 해당하는 속성을 선택하여 따로 모아 타입을 반환
- 제네릭 TYPE은 속성을 가지는 인터페이스나 객체 타입
type Pick<T, K extends keyof T> = { // 핵심은 제네릭 T 와 K의 관계를 extends로 먼저 조건을 명시해주어야 한다.
[P in K]: T[P];
};
Pick<TYPE, KEY>
interface User {
name: string;
age: number;
email: string;
isValid: boolean;
}
type Key = 'name' | 'email';
type Pick_User = Pick<User, Key>; // User 인터페이스의 속성에서 'name', 'email' 만 선택
/*
type Pick_User = {
name: string;
email: string;
}
*/
const user: Pick<User, Key> = {
name: 'inpa',
email: 'inpa@naver.com',
};
Omit<TYPE, KEY>
- 위에서 살펴본 Pick과 반대 버전
- 제네릭 TYPE으로 부터 제네릭 KEY에 해당하는 속성을 제외한 나머지들을 따로 모아 타입을 반환
- 제네릭 TYPE은 속성을 가지는 인터페이스나 객체 타입
// Pick 유틸리티 타입과 Exclude 유틸리티 타입을 응용해서 조합한 버전이라 할 수 있다
// 즉, 해당 Type을 Exclude한 나머지 타입들을 Pick
// extends keyof any 라는 뜻은 오로지 타입만 받게 하도록 설정
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Omit<TYPE, KEY>
interface User {
name: string;
age: number;
email: string;
isValid: boolean;
}
type Key = 'name' | 'email';
type Omit_User = Omit<User, Key>; // User 인터페이스의 속성에서 'name', 'email' 제외
/*
type Omit_User = {
age: number;
isValid: boolean;
}
*/
const user: Omit<User, Key> = {
age: 44,
isValid: true,
};
interface User {
name: string;
age: number;
email: string;
isValid: boolean;
}
// Omit이 되가는 과정 풀이
type Key = 'name' | 'email';
type User_Exclude = Exclude<keyof User, Key> // type User_Exclude = "age" | "isValid"
type User_Omit = Pick<User, User_Exclude>
/*
type User_Omit = {
age: number;
isValid: boolean;
}
*/
// 위의 풀이 과정을 한줄로 나타내면..
type User_Exclude_Pick2 = Pick<User, Exclude<keyof User, 'name' | 'email'>>
Exclude<TYPE1, TYPE2>
- 유니언 TYPE1에서 유니언 TYPE2를 제외한 나머지 타입을 반환
// 미리 정의된 조건부 타입
type Exclude<T, U> = T extends U ? never : T;
Exclude<TYPE1, TYPE2>
type Type = string | number | object;
type Exclude_Type = Exclude<Type, number>; // 유니온 Type의 string | number | object 에서 number을 제외
/*
type Exclude_Type = string | object
*/
const a: Exclude<Type, number> = 'Only string';
const b: Exclude<Type, number> = { name: '홍길동' };
const c: Exclude<Type, object> = 'Only string';
const d: Exclude<Type, object> = 123123;
type T0 = Exclude<("a" | "b" | "c"), ("a")>; // "b" | "c"
type T1 = Exclude<("a" | "b" | "c"), ("a" | "b")>; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
Extract<TYPE1, TYPE2>
- 유니언 TYPE1에서 유니언 TYPE2 과 겹치는 부분을 추출
type Extract<T, U> = T extends U ? T : never;
Extract<TYPE1, TYPE2>
type Type1 = string | number | object | null;
type Type2 = number | boolean;
type Extract_Type = Extract<Type1, Type2>; // 유니온 Type1에서 유니온 Type2 와 일치하는 타입 number만 추출
/*
type Extract_Type = number
*/
const a: Extract<Type1, Type2> = 123123;
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () => void
NonNullable<T>
- 유니온 Type에서 null과undefined를 제외한 타입을 반환
type NonNullable<T> = T extends null | undefined ? never : T;
NonNullable<Type>
type Type1 = string | number | object | null;
type Type2 = number | undefined;
type NonNullable_Type1 = NonNullable<Type1>; // 유니온 Type1에서 null 이나 undefined 를 제외
/*
type NonNullable_Type = string | number | object
*/
type NonNullable_Type2 = NonNullable<Type2>; // 유니온 Type2에서 null 이나 undefined 를 제외
/*
type NonNullable_Type2 = number
*/
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
Parameters<T>
- 함수를 제네릭 TYPE으로 받아, 함수의 매개변수 타입을 튜플(Tuple) 타입으로 반환
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Parameters<TYPE>
type Zip = { x: number; y: string; z: boolean };
function zip(x: number, y: string, z: boolean): Zip {
return { x, y, z };
}
type ZipType = typeof zip
// type ZipType = (x: number, y: string, z: boolean) => Zip
type Params = Parameters<typeof zip>
// type Params = [x: number, y: string, z: boolean]
type Frist = Params[0] // number
type Second = Params[0] // string
type Third = Params[0] // boolean
function fn(a: string | number, b: boolean) {
return `[${a}, ${b}]`;
}
type Parameters_Key = Parameters<typeof fn>; // 함수의 매개변수를 타입으로 변환
/*
type Parameters_Key = [string | number, boolean]
*/
const a: Parameters<typeof fn> = ['Hello', true];
const b: Parameters<typeof fn> = [123, false];
ConstructorParameters<T>
- 클래스를 제네릭 TYPE으로 받아, 클래스 생성자의 매개변수 타입을 새로운 튜플 타입으로 반환
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
ConstructorParameters<TYPE>
class User {
static father = '홍길동';
readonly mother = '귀부인';
constructor(public name: string, private age: number) {}
add() {}
}
const neo = new User('Neo', 12);
type Type = ConstructorParameters<typeof User> // 오로지 클래스 생성자의 매개변수들을 타입으로 변환
/*
type Type = [string, number]
*/
const a: ConstructorParameters<typeof User> = ['Neo', 12];
ReturnType<T>
- 함수를 제네릭 TYPE으로 받아, Return 타입을 반환
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
ReturnType<TYPE>
function fn(str: string): number {
return +str;
}
type Type = ReturnType<typeof fn>; // 함수의 리턴 타입을 반환
/*
type Type = number
*/
const a: ReturnType<typeof fn> = 1234;
const b: ReturnType<typeof fn> = 'Only string'; // TS2322: Type '123' is not assignable to type 'string'.
InstanceType<T>
- 클래스를 제네릭 TYPE으로 받아, 클래스의 생성자 인스턴스를 타입으로 반환
- 즉, new를 사용하여 생성된 인스턴스의 내용들을 타입으로 반환
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
InstanceType<TYPE>
class User {
constructor(public name: string, public age: number) {}
}
type Instance_Type = InstanceType<typeof User>; // 클래스의 생성자 인스턴스로 타입으로 변환해서 반환
/*
type Instance_Type = User
type Instance_Type = { name: string, age: number }
*/
const user: InstanceType<typeof User> = {
name: '홍길동',
age: 55,
};
class C {
x = 0;
y = 0;
}
type T0 = InstanceType<typeof C>; // 클래스의 기본 인스턴스를 타입으로 변환해서 반환
/*
type T0 = C
type T0 = { x: number, y: number }
*/
const tt: T0 = {
x: 100,
y: 200,
};
ThisParameterType<T>
- 함수를 제네릭 TYPE으로 받아, 명시적 this 매개변수 타입을 반환
- 만일 함수에 명시적 this 매개변수가 없는 경우 알 수 없는 타입(Unknown)을 반환
이 타입은 --strictFunctionTypes 옵션이 활성화되었을 때만 올바르게 동작한다
type ThisParameterType<T> = T extends (this: infer U, ...args: never) => any ? U : unknown;
ThisParameterType<TYPE>
interface ICat {
name: string;
}
const cat: ICat = {
name: 'Lucy',
};
function someFn(this: ICat, greeting: string) {
console.log(`${greeting} ${this.name}`); // ok
}
type Type = ThisParameterType<typeof someFn>; // 함수의 명시적 this 매개변수의 타입을 반환
/*
type Type = ICat
*/
someFn.call(cat, 'Hello'); // Hello Lucy
function toHex(this: Number) {
return this.toString(16);
}
// 매개변수 n의 타입은 toHex()의 this 매개변수 타입 Number
function numberToString(n: ThisParameterType<typeof toHex>) {
return toHex.apply(n);
}
OmitThisParameter<T>
- 함수를 제네릭 TYPE으로 받아, 명시적 this 매개변수를 제거한 타입 반환
이 타입은 --strictFunctionTypes 옵션이 활성화되었을 때만 올바르게 동작한다
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
OmitThisParameter<TYPE>
interface ICat {
age: number;
}
function getAge(this: ICat): number {
return this.age;
}
const cats = {
age: 12, // Number
};
// cats 타입 { age: number } 가 명시적 this의 타입 ICat 인터페이스와 일치하니 문제없이 동작
getAge.call(cats); // 12
/* --------------------------------------------------------------- */
const dogs = {
age: '13', // String
};
const mice = {
age: false, // Boolean
};
// 하지만 { age: number } 와 { age: number } 는 인터페이스 타입 형식에 맞지 않아 에러를 일으킴
getAge.call(dogs); // TS2345: Argument of type '{ age: string; }' is not assignable to parameter of type '{ age: number; }'.
getAge.call(mice); // TS2345: Argument of type '{ age: boolean; }' is not assignable to parameter of type '{ age: number; }'.
위 예제에서 데이터 cats을 기준으로 설계한 함수 getAge는 일부 다른 타입을 가지는 새로운 데이터 dogs 나 mice를 this로 사용할 수 없다.
하지만 OmitThisParameter를 통해 명시적 this를 제거한 새로운 타입의 함수를 만들 수 있기 때문에,
getAge를 직접 수정하지 않고 데이터 dog를 사용할 수 있게 된다.
주의할점은 명시적 this 타입을 제거했기 때문에, this의 타입은 any가 된다는 점이다.
type Type = OmitThisParameter<typeof getAge>; // 명시적 this 매개변수를 제외한 함수 타입을 반환
/*
type Type = (this: any) => number
*/
const getAgeForDog: OmitThisParameter<typeof getAge> = getAge; // 명시적 this 매개변수를 제외한 함수 타입을 반환
// 명시적 this 매개변수의 타입을 제외했으니, this의 타입은 any가 되서 아무 타입이나 다 들어갈 수 있게 된다.
getAgeForDog.call(dogs); // '13'
getAgeForDog.call(mice); // false
ThisType<T>
- 함수를 제네릭 TYPE으로 받아, this 컨텍스트(Context)를 TYPE으로 명시
이 유틸리티를 사용하기 위해선 --noImplicitThis 플래그를 사용해야 한다
interface ThisType<T> { }
ThisType<TYPE>
interface IUser {
name: string;
getName: () => string;
}
// methods 매개변수로 들어온 객체의 this를 IUser 타입으로 설정해준다.
function makeNeo(methods: ThisType<IUser>) {
return {
name: '홍길동',
...methods,
} as IUser; // 그리고 반환 객체값에 대한 타입을 IUser로 단언한다.
}
const person = makeNeo({
getName() {
return this.name;
}
}); // → const person: IUser = { name: 'Neo', getName() { return this.name; } }
const isname = person.getName(); // return this.name이 실행되서 현재 neo 객체의 'Neo'가 반환된다.
console.log('isname: ', isname); // '홍길동'
내장 문자열 조작 타입
템플릿 문자열 리터럴에서의 문자열 조작을 돕기 위해, 타입스크립트는 타입 시스템 내에서 문자열 조작에 사용할 수 있는 타입 집합이 포함 되어있다.
Uppercase<StringType>
- 문자열의 각 문자를 대문자로 변환하고 리터럴 타입으로 만듬
type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>; // type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
type MainID = ASCIICacheKey<"my_app">; // type MainID = "ID-MY_APP"
Lowercase<StringType>
- 문자열의 각 문자를 소문자로 변환하고 리터럴 타입으로 만듬
type Greeting = "Hello, world";
type QuietGreeting = Lowercase<Greeting>; // type QuietGreeting = "hello, world"
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`;
type MainID = ASCIICacheKey<"MY_APP">; // type MainID = "id-my_app"
Capitalize<StringType>
- 문자열의 첫 문자를 대문자로 변환하고 타입으로 만듬
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello, world"
Uncapitalize<StringType>
- 문자열의 첫 문자를 소문자로 변환하고 타입으로 만듬
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // type UncomfortableGreeting = "hELLO WORLD"
사용자 커스텀 유틸리티 타입
기본으로 제공하는 유틸리티 타입 이외에도, 맵드 타입과 조건부 타입 문법을 응용해 만든 유용한 사용자 커스텀 유틸리티 타입들을 몇개 소개해 본다.
Unpacked<T>
- 제네릭 T가 배열이면, 그 배열의 타입을 반환
- 제네릭 T가 함수이면, 함수의 리턴 타입을 반환
- 제네릭 T가 프로미스이면, 프로미스 값을 반환
- 위의 모든 조건을 만족하지 않을때 자기 자신을 반환
type Unpacked<T> = T extends (infer U)[]
? U // T가 어떤 값의 배열 (infer U)[] 이면 그 배열의 타입을 반환
: T extends (...args: any[]) => infer U
? U // 배열이 아니고 함수 타입이면, 함수 반환 타입을 반환
: T extends Promise<infer U>
? U // 배열, 함수도 아니고 프로미스 타입이면, 프로미스의 값을 반환
: T; // 위의 모든 조건이 만족하지않으면 T 자기 자신을 반환
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // string
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
StringPropertyNames<T>
- 인터페이스에서 값이 문자열인 속성 이름을 추출
type StringPropertyNames<T> = {
[K in keyof T]: T[K] extends string ? K : never; // 조건부 타입에서 string 인 것만 반환
}[keyof T]; // 그리고 속성의 밸류를 타입으로 반환
interface Person {
name: string;
age: number;
nation: string;
}
type T1 = StringPropertyNames<Person>;
/*
interface Person2 {
name: 'name';
age: never;
nation: 'nation';
}
에서 [keyof T] 로 인해 속성의 값을 타입으로 반환되어,
type T1 = "name" | "nation" 이 됨
*/
// ------------------------------------------------------
type StringProperties<T> = Pick<T, StringPropertyNames<T>>;
type T2 = StringProperties<Person>; // Pick<Person, ("name" | "nation")>
// Person 인터페이스에서 유니온 타입에 해당하는 속성만 선택
/*
type T2 = {
name: string;
nation: string;
}
*/
Overwrite<T, U>
- 제네릭 U를 T에 덮어 씌워 합침
// T와 U에서 겹치는 속성은 Exclude 하고 맵드 타입으로 순회해서 객체 타입으로 만들고, U와 인터렉션
type Overwrite<T, U> = { [P in Exclude<keyof T, keyof U>]: T[P] } & U;
interface Person {
name: string;
age: number;
}
// Person과 겹치는 age는 제거되고, { name: string } 이 반환 그리고 { age: string; nation: string } 와 인터렉션
type T1 = Overwrite<Person, { age: string; nation: string }>;
/*
type T1 = {
name: string;
age: string;
nation: string;
}
*/
const p: T1 = {
name: 'mike',
age: '23',
nation: 'korea',
};
ClassType<T>
- 클래스 그 자체를 묘사하는 타입
- 직접 클래스 타입 만들기
interface ClassType<T, A extends any[] = any[]> extends Function {
new (...args: A): T;
}
class Foo {
private c: boolean;
constructor(public a: string, b: number) {
this.c = !b;
}
}
/**
* 첫번째 인자에 클래스를 넣고,
* 두번째 인자에 그 클래스의 생성자가 받는 파라미터의 타입을 튜플 타입으로 넣어준다.
* 파라미터 타입이 자동으로 추론 되지 않다보니 다소 불편하긴 하다.
*/
type FooClass = ClassType<Foo, [string, number]>;
const MyFooClass: FooClass = Foo;
// MyFooClass 의 인스턴스가 Foo 와 같기때문에 타입에러가 나지 않는다.
const foo: Foo = new MyFooClass('def', 5678);
- 클래스를 함수 파라미터의 타입으로 사용해 생성하기
interface ClassType<T, A extends any[] = any[]> extends Function {
new (...args: A): T;
}
/**
* 함수의 제네릭으로 ClassType의 제네릭과 같은 제네릭을 선언해주고,
* 함수의 첫번째 파라미터로 ClassType을 선언한다.
* 생성자 파라미터 `A` 는 함수 안에서 자동으로 추론 된다.
*/
const createInstance = <T, A extends any[]>(classToCreate: ClassType<T, A>, constructorArgs: A) => {
return new classToCreate(...constructorArgs);
};
class Foo {
private c: boolean;
constructor(public a: string, b: number) {
this.c = !b;
}
}
// 아주 잘 작동한다.
const foo: Foo = createInstance(Foo, ['abc', 1234]);
# 참고자료
https://heropy.blog/2020/01/27/typescript/
https://typescript-kr.github.io/pages/utility-types.html
https://velog.io/@vraimentres/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-%ED%83%80%EC%9E%85%EC%9D%84-%ED%91%9C%ED%98%84%ED%95%98%EA%B8%B0
https://velog.io/@zeros0623/TypeScript-%EA%B3%A0%EA%B8%89-%ED%83%80%EC%9E%85
쉽게 시작하는 타입스크립트 (길벗, 2023, 캡틴판교 지음)
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.