Type Manipulation
Creating Types from Types
- Generics
function echo<Type>(arg: Type): Type {
return arg;
}
let output = echo<string>('Test'); // string 출력
let output2 = echo('Test'); // string 출력, 타입 추론
function echoArr<Type>(arg: Type[]): Type[] {
// 같은 의미
// function echoArr<Type>(arg: Array<Type>): Array<Type> {
console.log(arg.length);
return arg;
}
let output3 = echoArr([1,2,3]); // 3, [1,2,3] 출력
△제네릭 함수
let myEcho: { <Type>(arg: Type): Type } = echo;
// or
interface GenericEchoFn {
<Type>(arg: Type): Type;
}
let myEcho: GenericEchoFn = echo;
// or
interface GenericEchoFn<Type> {
(arg: Type): Type;
}
let myEcho: GeneircEchoFn<string> = echo;
// do not
// because TS parser doesn't support a naked generic arrow function
let myEcho: <Type>(arg: Type) => Type;
△ 제네릭 타입
class GenericClass<Type> {
val: type;
func: (x: Type, y: Type) => Type;
}
let myGenericClass = new GenericClass<string>();
myGenericClass.val = 'test';
myGenericClass.func = function (x,y) {
return x + y;
};
△ 제네릭 클래스(인스턴스 측면에서만 사용)
제네릭 열거형(enums)과 네임스페이스(namespace)는 만들 수 없습니다.
제네릭 제약조건
// length 속성을 가진 타입으로 제한하고 싶은 경우
interface ExtendLength {
length: number;
}
function Test<Type extends ExtendLength>(arg: Type): Type {
console.log(arg.length);
return arg;
}
function createInstance<Type>(c: {new (): Type}): Type {
return new c();
}
- Keyof Type Operator
keyof 연산자는 객체 타입에서 객체의 키 값을 리터럴 유니언을 생성
type Test = {x: number; y: string};
type T = keyof Test; // 'x' | 'y'
type Test = { [n: number]: unknown };
type T = keyof Test; // number
type Test = { [s: string]: boolean };
type T = keyof Test; // string | number
// string | number인 이유는 객체 키는 항상 문자열이기 때문에 obj[0]과 obj['0']은 동일
- Typeof Type Operator
typeof 연산자는 해당 변수나 속성의 타입을 추론할 수 있다.
console.log(typeof 'str'); // string
// 예를 들어 ReturnType<T>을 사용하면 함수 타입을 받아 반환 타입을 제공합니다.
type Func = (x: unknown) => string;
type R = ReturnType<Func>; // string;
// 하지만 함수 이름을 넣으면 오류가 납니다.
function func() {
return { x:10, y:'oh'};
}
type R = ReturnType<func>;
// error: 'func' refers to a value, but is being used as a type here. Did you mean 'typeof func'?
// 이유는 값과 타입은 같지 않다!!
// == 함수 값인 func과 func의 타입인 typeof func의 차이가 있다.
type R = ReturnType<typeof func>; // {x:number; y:string;}
typeof 키워드의 제한 사항으로 식별자 또는 속성(property)에서만 사용할 수 있다.
- Indexed Access Types
특정 속성을 찾기 위한 인덱싱 접근
type Test = {val: number; str: string; bool: boolean;};
type Val = Test['val']; // number
type I1 = Test['val' | 'bool']; // number | boolean
type I2 = Test[keyof Test]; // number | string | boolean
// 배열의 인덱스를 number로 접근해서 가져올 수 있다.
const Arr = [{str:'str', num: 10}];
type T = typeof Arr[number]; // {str:string; num:number;}
// 인덱싱을 할 때는 값을 사용할 수 없고, 타입만 사용가능하다.
const key = 'val';
type Val = Test[key]; // 오류 발생
- Conditional Types
삼항 연산자 조건문과 같은 형태로 타입에도 사용할 수 있다.
// 왼쪽 타입(Some)이 오른쪽 타입(Thing)에 할당할 수 있다면 참
type Example = Some extends Thing ? number : string;
// 제네릭과 함께 사용하면 좋다
type One {
val: number;
}
type More {
val: string;
}
type OneOrMore<T extends number | string> = T extends number ? One : More;
function createSome<T extends number | string>(oneOrMore: T): OneOrMore<T> {
throw 'unimplemented';
}
infer 키워드를 사용하면 '참' 값 분기에서 비교하는 타입을 추론할 수 있는 것 같다. 그런데 어떻게 사용하는지는 아직 이해불가
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
여러 호출 시그니처를 가진 타입을 추론할 때 마지막 시그니처로 추론
유니언 타입을 제네릭에 넘기게 되면 각 타입에 조건이 적용. 방지하기 위해선 extends 키워드의 양 옆으로 []로 감싸기
type ToArr<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
// 분산 방지
type ToArray<Type> = [Type] extends [any] ? Type[] : never; // (string | number)[]
- Mapped Types
type Example = {
[key: type] : type;
}
type GenericExample<Type> = {
[property in keyof Type]: type;
// -,+를 통해 readonly 수정자 추가 가능. 기본값은 +
readonly [property in keyof Type]; Type[Property];
-readonly [property in keyof Type]; Type[Property];
// -,+를 통해 ?(optional) 수정자 추가 가능. 기본값은 +
[property in keyof Type]-?: type;
[property in keyof Type]?: type;
// TS 버전 4.1 이상에서는 as를 사용해 별칭 가능
[property in keyof Type as something]: type;
// 조건부 타입을 통해 never를 생성하고 키 필터링
[property in keyof Type as Exclude<property, ''>]: Type[property];
}
- Template Literal Types
기존 JS에서 사용되는 템플릿 리터럴 문자열과 같은 문법을 사용하지만 type에서 사용한다는 점이 다르다.
type Some = 'some';
type Thing = `${Some} thing`; // 'some thing' 타입
// union이 사용된 타입은 전부 적용된다.
type Some = 'some' | 'other';
type Thing = 'thing' | 'Thing';
type UnionType = `${Some | Thing} new!`; // "some new!" | "other new!" | "thing new!" | "Thing new!"
type UnionTyp2 = `${Some}_${Thing}`; // "some_thing" | "some_Thing" | "other_thing" | "other_Thing"
예를 들어 기존 함수에 on 함수를 추가하고 싶은데 해당 함수의 첫 인자가 기존 함수가 가진 객체 타입 속성값에 Changed만 추가된 경우라면 템플릿 리터럴 타입을 활용하면 쉽게 확인할 수 있다.
// 해당 타입에서 `${string & keyof Type}Changed`를 이용하면
// 쉽게 기존 속성 이름에 Changed만 추가할 수 있다.
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
person.on("firstNameChanged", () => {});
person.on("firstName", () => {}); // error 속성 이름이 잘못되었다.
그다음은 기존 방식으로는 newValue의 타입이 any가 아닌 string으로 전환하고 싶다면 할 수 있는 방법이 나오는데 한 번에 이해할 수 없을 것 같아 넘깁니다.
타입에서 조작할 수 있는 방법들
- Uppercase<StringType>: 대문자로 변경
- Lowercase<StringType>: 소문자로 변경
- Capitalize<Stringtype>: 시작 글자 대문자로 변경
- Uncapitalize<Stringtype>: 시작 글자 소문자로 변경
참고
https://www.typescriptlang.org/docs/handbook/2/types-from-types.html
'개발 > TS' 카테고리의 다른 글
| [TS] TS Doc Handbook 1회독 - 8 (0) | 2025.06.20 |
|---|---|
| [TS] TS Doc Handbook 1회독 - 7 (0) | 2025.06.17 |
| [TS] TS Doc Handbook 1회독 - 5 (1) | 2025.06.13 |
| [TS] TS Doc Handbook 1회독 - 4 (0) | 2025.03.04 |
| [TS] TS Doc Handbook 1회독 - 3 (0) | 2025.02.28 |