function identity(arg: number): number {
return arg;
}
// 제네릭이 없다면 any로 처리해야하며, 반환시 타입 정보를 잃어버리게 됨
function identity(arg: any): any {
return arg; // 입력 타입에 상관없이 any
}
// 제네릭 사용
function identity<Type>(arg: Type): Type {
return arg; // 입력 타입에 맞는 타입 반환
}
//타입 명시
let output = identity<string>("myString"); //string
//타입 추론
let output = identity("123"); //number
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length); // Property 'length' does not exist on type 'Type'.
return arg;
}
// 타입 범위를 배열로 좁혀줘야 함
function loggingIdentity<Type>(arg: Type[]): Type[] {
console.log(arg.length); // ok
return arg;
}
function identity<Type>(arg: Type): Type {
return arg;
}
// 제네릭의 이름은 임의로 정해도 상관 없음
let myIdentity: <Input>(arg: Input) => Input = identity;
// 리터럴 타입의 호출 시그니처로도 작성 가능
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: { <Type>(arg: Type): Type } = identity;
// 인터페이스로도 사용 가능
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
interface Lengthwise {
length: number;
}
// type에는 length 속성이 있는 것들만 들어올 수 있음
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]);
loggingIdentity(3); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
loggingIdentity({ length: 10, value: 3 });
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
// Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
getProperty(x, "m");
function create<Type>(c: { new (): Type }): Type {
return new c();
}
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
type Point = { x: number; y: number };
type P = keyof Point; // "x" | "y" 타입
// 인덱스 시그니쳐를 가지면, 해당 타입 반환
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
// string이 키값으로 들어오면 타입은 string | number로 변환됨
// 자바스크립트에서 객체 키는 항상 문자열이 되기 때문에 obj[0](=obj["0"])처럼 숫자가 들어올 수 있기 때문
type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
console.log(typeof "Hello world"); //string
let s = "hello";
let n: typeof s; // n: string
// 변수 혹은 프로퍼티에서만 사용 가능
// ',' expected.
let shouldContinue: typeof msgbox("Are you sure you want to continue?");
type Predicate = (x: unknown) => boolean;
//ReturnType<T>, 타입이 아닌 값을 반환받음에 주목
type K = ReturnType<Predicate>; //k = boolean
function f() {
return { x: 10, y: 3 };
}
// 'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?
type P = ReturnType<f>;
// f의 값을 추론하기 위해서는 typeof 사용 필요
type P = ReturnType<typeof f>;
/*type P = {
x: number;
y: number;
}
*/
declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;
type T1 = ReturnType<typeof stringOrNum>; // string | number
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string; //number
type Example2 = RegExp extends Animal ? number : string; //string
// 2개의 타입을 결합하려고만 해도 코드가 매우 길어짐
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}
// 제네릭과 조건부 타입 사용
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
// 메시지 여부에 따라 메시지 속성의 타입이나 never 반환
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
type EmailMessageContents = MessageOf<Email>; //string
type DogMessageContents = MessageOf<Dog>; //never
// 배열의 타입을 평탄화시키는 예시
type Flatten<T> = T extends any[] ? T[number] : T;
type Str = Flatten<string[]>; //string
type Num = Flatten<number>; //number
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never;
type Num = GetReturnType<() => number>; //number
type Str = GetReturnType<(x: string) => string>; //string
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>; //boolean[]
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; //string[], number
//(string | number)[]로 동작시키려면 extend 키워드 양쪽을 대괄호로 감싸야 함
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr = ToArrayNonDist<string | number>; //(string | number)[]
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};
// OnlyBoolsAndHorses 타입 바탕으로 새로운 타입 생성
const conforms: OnlyBoolsAndHorses = {
del: true,
rodney: false,
};
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
//OptionFlags에 의해 타입이 맵핑되 FeatureFlags 속성을 가지고 OptionsFlags의 값을 가지게 됨
type FeatureOptions = OptionsFlags<FeatureFlags>;
// ex
type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
// LockedAccount에서 readonly가 사라진 타입
type UnlockedAccount = CreateMutable<LockedAccount>;
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
// name, age는 더이상 optional이 아님
type User = Concrete<MaybeUser>;
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}
//모든 타입의 조합을 임의로 매핑 가능
type EventConfig<Events extends { kind: string }> = {
[E in Events as E["kind"]]: (event: E) => void;
}
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>
// as 타입을 사용해 kind 속성을 제거하는 예시
// Exclude<a, b> -> a 타입에서 b에 해당되는 타입을 제거한 타입 반환
type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
kind: "circle"; // Exclude에 의해 제외됨
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
type Greeting = `hello ${World}`; //type: hello World
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
// type: "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
// 12개의 타입 조합이 가능
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
person.on("firstNameChanged", (newValue) => {
console.log(`firstName was changed to ${newValue}!`);
});
// firstNameChanged, lastNameChanged, ageChanged 식으로 값이 들어오면 템플릿 리터럴을 사용해볼 수 있다.
//여전히 any가 사용되고 있음에 주목
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
person.on("firstNameChanged", () => {}); //ok
person.on("firstName", () => {}); //error
person.on("frstNameChanged", () => {}); //error
// any를 없에기 위해 generic 사용해 완성
type PropEventSource<Type> = {
on<Key extends string & keyof Type>
(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ): void;
};
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
person.on("firstNameChanged", newName => {
console.log(`new name is ${newName.toUpperCase()}`);
});
person.on("ageChanged", newAge => {
if (newAge < 0) {
console.warn("warning! negative age");
}
})
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting> //type: "HELLO, WORLD"
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting> //type: "hello, world"
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>; //type: "Hello, world"
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; //type: "hELLO, WORLD"
출처:
https://www.typescriptlang.org/ko/docs/handbook/2/generics.html