[K in OriginalType]
과 같이 in을 사용해 다른 타입으로부터 계산된 타입을 사용합니다.type NewType = {
[K in OriginalType]: NewProperty;
};
type Animals = "alligator" | "baboon" | "cat";
type AnimalCounts = {
[K in Animals]: number;
}
// {
// alligator: number;
// baboon: number;
// cat: number;
//}
존재하는 union 리터럴을 기반으로 매핑된 타입은 큰 interface를 선언하는 공간을 절약하는 편리한 방법입니다.
매핑된 타입은 다른 타입에 대해 작동하고 멤버에서 제한자를 추가하거나 제거할 수 있을 때 정말로 유용해집니다.
interface AnimalVariants {
alligator: boolean;
baboon: number;
cat: string;
}
type AnimalCounts = {
[K in keyof AnimalVariants]: number;
};
// {
// alligator: number;
// baboon: number;
// cat: number;
//}
[K in keyof SomeName]
인 경우 매핑된 타입의 각 멤버는 SomeName 멤버의 값을 SomeName[k]
로 참조할 수 있습니다.interface BirdVariants {
dove: string;
eagle: boolean;
}
type NullableBirdVariants = {
[K in keyof BirdVariants]: BirdVariants[K] | null;
};
// {
// dove: string | null;
// eagle: boolean | null;
//}
TypeScript가 interface 멤버를 함수로 선언하는 두 가지 방법을 제공합니다.
member(): void
같은 메서드 구문 : interface 멤버가 객체의 멤버로 호출되도록 의도된 함수임을 선언member: () => void
같은 속성 구문 : interfece 멤버가 독립 실행형 함수와 같다고 선언매핑된 타입은 객체 타입의 메서드와 속성 구문을 구분하지 않습니다.
interface Researcher {
researchMethod(): void;
researchProperty: () => string;
}
type JustProperties<T> = {
[K in keyof T]: T[K];
};
type ResearcherProperties = JustProperties<Researcher>;
// {
// researcherMethod: () => void;
// researcherProperty: () => string;
// }
interface Environmentalist {
area: string;
name: string;
}
type ReadonlyEnvironmentalist = {
readonly [K in keyof Environmentalist]: Environmentalist[K];
};
// {
// readonly area: string;
// readonly name: string;
// }
type OptionalReadonlyEnvironmentalist = {
[K in keyof ReadonlyEnvironmentalist]?: ReadonlyEnvironmentalist[K];
};
// {
// readonly area?: string;
// readonly name?: string;
// }
type OptionalReadonlyEnvironmentalist2 = {
readonly [K in keyof Environmentalist]?: Environmentalist[K];
};
// {
// readonly area?: string;
// readonly name?: string;
// }
-
를 추가해 제한자를 제거할 수 있습니다.interface Conservationist {
name: string;
catchphrase?: string;
readonly born: number;
readonly died?: number;
}
type WritableConservationist = {
-readonly [K in keyof Conservationist]: Conservationist[K];
};
// {
// name: string;
// catchphrase?: string | undefined;
// born: number;
// died?: number | undefined;
// }
type RequiredWritableConservationist = {
[K in keyof WritableConservationist]-?: Conservationist[K];
};
// {
// name: string;
// catchphrase?: string;
// born: number;
// died?: number;
// }
type RequiredWritableConservationist2 = {
-readonly [K in keyof Conservationist]-?: Conservationist[K];
};
// {
// name: string;
// catchphrase?: string;
// born: number;
// died?: number;
// }
type MakeReadonly<T> = {
readonly [K in keyof T]: T[K];
}
interface Species {
genus: string;
name: string;
}
type ReadonlySpecies = MakeReadonly<Species>;
// {
// readonly genus: string;
// readonly name: string;
// }
interface GenusData {
family: string;
name: string;
}
type MakeOptional<T> = {
[K in keyof T]?: T[K];
}
type OptionalGenusData = MakeOptional<GenusData>;
// {
// family?: string | undefined;
// name?: string | undefined;
// }
function createGenusData(overrides?: OptionalGenusData): GenusData {
// GenusData의 기본값('unknown') 위에 모든 {overrides}를 구조 분해 할당함
return {
family: 'unknown',
name: 'unknown',
...overrides,
}
}
TypeScript의 타입 시스템은 논리 프로그래밍 언어의 한 예입니다.
조건부 타입 구문은 삼항 연산자 조건문처럼 보입니다.
LeftType extends RightType ? IfTrue : IfFalse
// 타입 : false
type CheckStringAgainstNumber = string extends number ? true : false;
type CheckAgainstNumber<T> = T extends number ? true : false;
// 타입 : false
type CheckString = CheckAgainstNumber<'parakeet'>;
// 타입 : true
type CheckNumber = CheckAgainstNumber<1891>;
// 타입 : true
type CheckNumberType = CheckAgainstNumber<number>;
type CallableSetting<T> = T extends () => any ? T : () => T
// 타입 : () => number[]
type GetNumbersSetting = CallableSetting<() => number[]>;
// 타입 : () => string
type StringSetting = CallableSetting<string>;
조건부 타입은 객체 멤버 검색 구문을 사용해서 제공된 타입의 멤버에 접근할 수 있고, extends 절과 결과 타입에서 그 정보를 사용할 수 있습니다.
JavaScript 라이브러리에서 사용하는 패턴 중 조건부 제네릭 타입에도 적합한 한 가지 패턴은 함수에 제공된 옵션 객체를 기반을 함수의 반환 타입을 변경하는 것입니다.
interface QueryOptions {
throwIfNotFound: boolean;
}
type QueryResult<Options extends QueryOptions> =
Options["throwIfNotFound"] extends true ? string : string | undefined;
declare function retrieve<Options extends QueryOptions>(
key: string,
options?: Options
): Promise<QueryResult<Options>>;
// 반환된 타입 : string | undefined
await retrieve("Birute Galdikas");
// 반환된 타입 : string | undefined
await retrieve("jane Goodall", { throwIfNotFound: Math.random() > 0.5 });
// 반환된 타입 : string
await retrieve("Dian Fossey", { throwIfNotFound: true });
ConditionalType<T | U> === Conditional<T> | Conditional<U>
와 같습니다.type ArrayifyUnlessString<T> = T extends string ? T : T[];
// 타입 : ArrayifyUnlessString<string> | ArrayifyUnlessString<number>
// 타입 : string | number[]
type HalfArrayified = ArrayifyUnlessString<string | number>;
제공된 타입의 멤버에 접근하는 것은 타입의 멤버로 저장된 정보에 대해서는 잘 작동하지만 함수 매개변수 또는 반환 타입과 같은 다른 정보에 대해서는 알 수 없습니다.
조건부 타입은 extends 절에 infer 키워드를 사용해 조건의 임의의 부분에 접근합니다.
다음 ArrayItems 타입은 타입 매개변수 T를 받고 T가 새로운 Item 타입의 배열인지 확인합니다.
type ArrayItems<T> = T extends (infer Item)[] ? Item : T;
// 타입 : string
type StringItem = ArrayItems<string>;
// 타입 : string
type StringArrayItem = ArrayItems<string[]>;
// 타입 : string[]
type String2DItem = ArrayItems<string[][]>;
유추된 타입은 재귀적 조건부 타입을 생성하는 데에도 사용할 수 있습니다.
다음 ArrayItemsRecursive는 이전의 ArrayItems 타입이 모든 차원 배열의 item 타입을 재귀적으로 검색하도록 확장했습니다.
type ArrayItemsRecursive<T> = T extends (infer Item)[] ? ArrayItemsRecursive<Item> : T;
// 타입 : string
type StringItem = ArrayItemsRecursive<string>;
// 타입 : string
type StringArrayItem = ArrayItemsRecursive<string[]>;
// 타입 : string
type String2DItem = ArrayItemsRecursive<string[][]>;
매핑된 타입은 기존 타입의 모든 멤버에 변경 사항을 적용하고 조건부 타입은 하나의 기존 타입에 변경 사항을 적용합니다.
다음 MakeAllMembersFunctions 타입은 함수가 아닌 각 멤버를 함수로 바꿉니다.
type MakeAllMembersFunctions<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : () => T[K];
};
type MemberFunctions = MakeAllMembersFunctions<{
alreadyFunction: () => string,
notYetFunction: number,
}>;
// {
// alreadyFunction: () => string;
// notYetFunction: () => number;
// }
// 타입 : never
type NeverIntersection = never & string;
// 타입 : string
type NeverUnion = never | string;
type OnlyStrings<T> = T extends string ? T : never;
// 타입 : "red" | "blue"
type RedOrBlue = OnlyStrings<"red" | "blue" | 0 | false>;
never는 또한 제네릭 타입에 대한 타입 유틸리티를 만들 때 유추된 조건부 타입과 결합됩니다.
다음 FirstParameter 타입은 함수 타입 T를 받고, arg: infer Arg가 함수인지 확인하고, 함수가 맞다면 Arg를 반환합니다.
type FirstParameter<T extends (...args: any[]) => any> =
T extends (arg : infer Arg) => any ? Arg : never;
// 타입 : string
type GetsString = FirstParameter<(arg0: string) => void>;
union에서 never의 동작은 매핑된 타입에서 멤버를 필터링할 때도 유용합니다.
다음 세 가지 타입 시스템 기능을 사용해 객체의 키를 필터링합니다.
세 가지 기능을 함께 사용하면 원래 타입의 각 멤버를 원래 키 또는 never로 변경하는 매핑된 타입을 만들 수 있습니다.
[keyof T]
로 해당 타입의 멤버를 요청하면 모든 매핑된 타입의 결과 union이 생성되고 never는 필터링됩니다.다음 OnlyStringProperties 타입은 각 T[K] 멤버가 string인 경우 K 키로 변경하고, string이 아닌 경우 never로 변경합니다.
type OnlyStringProperties<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
interface AllEventData {
participants: string[];
location: string;
name: string;
year: number;
}
// 타입 : "location" | "name"
type OnlyStringEvent = OnlyStringProperties<AllEventData>;
type Greeting = `Hello${string}`;
// Ok
let matches: Greeting = "Hello, world!";
// Error: Type "World! Hello!" is not assignable to type `Hello${string}`
let outOfOrders: Greeting = "World! Hello!";
// Error: Type "hi" is not assignable to type `Hello${string}`
let missingAlTogether: Greeting = "hi";
type Brightness = "dark" | "light";
type Color = "blue" | "red";
// 타입: type Brightness = "dark-blue" | "dark-red" | "light-blue" | "light-red"
type BrightnessAndColor = `${Brightness}-${Color}`;
// Ok
let colorOk: BrightnessAndColor = "dark-blue";
// Error: Type '"medium-blue"' is not assignable to type
// '"dark-blue" | "dark-red" | "light-blue" | "light-red"'.
let colorWrongStart: BrightnessAndColor = "medium-blue";
// Error: Type '"light-green"' is not assignable to type
// '"dark-blue" | "dark-red" | "light-blue" | "light-red"'.
let colorWrongEnd: BrightnessAndColor = "light-green";
type ExtolNumber = `much ${number} wow`;
function extol(extolee: ExtolNumber) { /* ... */ }
// Ok
extol('much 0 wow');
// Ok
extol('much -7 wow');
// Ok
extol('much 9.001 wow');
// Error: Argument of type '"much false wow"' is not
// assignable to parameter of type '`much ${number} wow`'.
extol('much false wow');
type FormalGreeting = Capitalize<"hello.">; // 타입 : "Hello."
type DataKey = "location" | "name" | "year";
type ExistenceChecks = {
[K in `check${Capitalize<DataKey>}`]: () => boolean;
};
// {
// checkLocation: () => boolean;
// checkName: () => boolean;
// checkYear: () => boolean;
// }
function checkExistence(checks: ExistenceChecks) {
checks.checkLocation(); // 타입 : boolean
checks.checkName(); // 타입 : boolean
// Error: Property 'checkWrong' does not exist on type 'ExistenceChecks'.
checks.checkWrong();
}
interface DataEntry<T> {
key: T;
value: string;
}
type DataKey = "location" | "name" | "year";
type DataEntryGetters = {
[K in DataKey as `get${Capitalize<K>}`]: () => DataEntry<K>;
};
// {
// getLocation: () => DataEntry<"Location">;
// getName: () => DataEntry<"Name">;
// getYear: () => DataEntry<"Year">;
// }
const config = {
location: "unknown",
name: "anonymous",
year: 0,
};
type LazyValues = {
[K in keyof typeof config as `${K}Lazy`]: () => Promise<typeof config[K]>;
};
// {
// location: Promise<string>;
// name: Promise<string>;
// year: Promise<number>;
// }
async function withLazyValues(configGetter: LazyValues) {
// 타입 : string
await configGetter.locationLazy;
// Error:
const config = {
location: "unknown",
name: "anonymous",
year: 0,
};
type LazyValues = {
[K in keyof typeof config as `${K}Lazy`]: () => Promise<typeof config[K]>;
};
// {
// location: Promise<string>;
// name: Promise<string>;
// year: Promise<number>;
// }
async function withLazyValues(configGetter: LazyValues) {
// 타입 : string
await configGetter.locationLazy;
// Error: Property 'missingLazy' does not exist on type 'LazyValues'.
await configGetter.missingLazy;
}
type TurnIntoGettersDirect<T> = {
[K in keyof T as `get${K}`]: () => T[K]
}
// Type 'K' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
// Type 'keyof T' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
// Type 'string | number | symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
// Type 'symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
string & symbol
은 never가 되므로 전체 템플릿 문자열은 never가 되고 TypeScript는 이를 무시하게 됩니다.const someSymbol = Symbol("");
interface HasStringAndSymbol {
StringKey: string;
[someSymbol]: number;
}
type TurnIntoGetters<T> = {
[K in keyof T as `get${string & K}`]: () => T[K]
};
type GetterJustString = TurnIntoGetters<HasStringAndSymbol>;
const someSymbol = Symbol("");
interface HasStringAndSymbol {
StringKey: string;
[someSymbol]: number;
}
type TurnIntoGetters<T> = {
[K in keyof T as `get${string & K}`]: () => T[K]
};
type GetterJustString = TurnIntoGetters<HasStringAndSymbol>;
// {
// getStringKey: () => string;
// }