// 가장 단순한 함수인 identity:const identity = (value) => value;// 는 다음과 같이 변합니다.type Identity<Type> = Type;// 그리고 이렇게 호출할 수 있습니다.type Result = Identity<number>;// type Result = number// 조금 더 복잡한 함수const createObject = (a, b) => ({ a, b });// 는 다음과 같이 변합니다.type CreateObject<A, B> = { a: A, b: B };// 그리고 이렇게 호출할 수 있습니다.type Result = CreateObject<string, boolean>;// type Result = { a: string; b: boolean }extends 키워드는 함수 인자에 타입을 지정하는 것처럼, 타입 매개변수가 특정 타입을 확장해야 함을 TypeScript에 알려줍니다.type CreateObject<A extends string, B> = { a: A, b: B };// A는 반드시 string이어야 하며, 그렇지 않으면 오류가 발생합니다.type Errored = CreateObject<number, boolean>;// 'number' 타입이 'string' 제약 조건을 만족하지 않습니다.type Result = CreateObject<'name', boolean>;// type Result = { a: "name"; b: boolean; }type CreateObject< Key extends string = 'defaultName', Value = string> = { [Key]: Value };// 이제 Key와 Value는 선택 사항입니다.type Result = CreateObject;// type Result = { defaultName: string }type Result2 = CreateObject<'name'>;// type Result2 = { name: string; }type Result3 = CreateObject<'name', boolean>;// type Result3 = { name: boolean; }type Crud<Resource extends { id: string | number }> = { // create는 id를 제외한 모든 필드를 받습니다. create: (resource: Omit<Resource, 'id'>) => Resource; // getOne은 id를 받아 리소스 또는 undefined를 반환합니다. getOne: (id: Resource['id']) => Resource | undefined; // update는 id와 (id를 제외한) 부분 리소스를 받아 // 리소스 또는 undefined를 반환합니다. update: ( id: Resource['id'], resource: Partial<Omit<Resource, 'id'>> ) => Resource | undefined; // delete는 id를 받아 // 리소스 삭제 여부를 나타내는 boolean을 반환합니다. delete: (id: Resource['id']) => boolean; // getList는 (id를 제외한) 부분 리소스를 필터로 받아 // 리소스 배열을 반환합니다. getList: (filter: Partial<Omit<Resource, 'id'>>) => Resource[];}// 사용 예시type User = { id: number; name: string; email: string };type UserCrud = Crud<User>;// type UserCrud = {// create: (resource: Omit<User, 'id'>) => User;// getOne: (id: number) => User | undefined;// update: (id: number, resource: Partial<Omit<User, 'id' >>) =>// User | undefined;// delete: (id: number) => boolean;// getList: (filter: Partial<Omit<User, 'id'>>) => User[];// }extends 키워드를 사용하여 조건을 만들 수 있습니다.
extends는 한 타입이 다른 타입의 일부인지 테스트합니다. 그런 다음 삼항 연산자 구문인 condition ? trueCase : falseCase를 사용합니다.type IsNumber<Value extends unknown> = Value extends number ? true : false;type Result = IsNumber<7>// type Result = truetype Result2 = IsNumber<'seven'>;// type Result = falsetype CreateEvent = { type: "create"; payload: { name: string }};type UpdateEvent = { type: "update"; payload: { id: number; name?: string }};type DeleteEvent = { type: "delete"; payload: { id: number }};type UnknownEvent = unknown;type Event = CreateEvent | UpdateEvent | DeleteEvent | UnknownEvent;type InferEventType<T extends Event> = T extends CreateEvent ? "create" : T extends UpdateEvent ? "update" : T extends DeleteEvent ? "delete" : never;// 사용 예시type EventType = InferEventType<UpdateEvent>; // 'update'type EventType2 = InferEventType<CreateEvent>; // 'create'type EventType3 = InferEventType<DeleteEvent>; // 'delete'type EventType4 = InferEventType<'An event'>; // neverinfer 키워드: 변수infer 키워드는 타입 정의 내부에 변수를 생성합니다. 이는 구조 분해 할당(Destructuring)을 지원합니다.// 먼저, infer 키워드를 사용하여 배열의 첫 번째 요소를// 구조 분해한 뒤 반환합니다.type First<Element> = Element extends [infer FirstElement, ...any[]] ? FirstElement : never// 사용 예시type Result = First<[string, number, boolean]>;// type Result = stringtype Result2 = First<[]>;// type Result2 = neverinfer를 사용하면 조건부 타입 섹션의 이벤트 예제를 다음과 같이 단순화할 수 있습니다.type InferEventType< T extends Event> = T extends { type: infer EventType } ? EventType : never;// T에서 type 필드를 추출하는 변수 EventType을 정의합니다.// 각 이벤트 타입을 개별적으로 확인할 필요가 없습니다.// 사용 예시type EventType = InferEventType<UpdateEvent>; // 'update'type EventType2 = InferEventType<CreateEvent>; // 'create'type EventType3 = InferEventType<DeleteEvent>; // 'delete'type EventType4 = InferEventType<{}>; // nevertype TreeNode<Value> = { value: Value; children?: TreeNode<Value>[];}type StringTree = TreeNode<string>;const tree: StringTree = { value: 'root', children: [ { value: 'child1' }, { value: 'child2', children: [ { value: 'grandchild1' } ] } ]};Find 타입을 구현해 보겠습니다. 찾으면 해당 타입을 반환하고, 그렇지 않으면 never를 반환합니다.type Find<ArrayType extends unknown[], ValueType> = // 배열을 첫 번째 요소와 나머지로 구조 분해합니다. ArrayType extends [infer First, ...infer Rest] // 첫 번째 요소가 우리가 찾는 타입인지 확인합니다. ? First extends ValueType // 맞다면 해당 요소를 반환합니다. ? First // 아니라면 배열의 나머지에 대해 재귀를 수행합니다. : Find<Rest, ValueType> : never; // 배열이 비어있으면 never를 반환합니다.// 이벤트를 사용한 예시type EventsArray = [{ type: 'create'; payload: { name: string; };}, { type: 'update'; payload: { id: number; name?: string; };}, { type: 'delete'; payload: { id: number; };}];type CreateEvent = Find< EventsArray, { type: 'create'; payload: { name: string; }; }>;// type CreateEvent = { type: "create"; payload: { name: string; }; }type UpdateEvent = Find< EventsArray, { type: 'update'; payload: { id: number; name?: string; }; }>;// type UpdateEvent = {// type: "update";// payload: { id: number; name?: string | undefined; };// }type DeleteEvent = Find< EventsArray, { type: 'delete'; payload: { id: number; }; }>;// type DeleteEvent = { type: "delete"; payload: { id: number; }; }type UnknownEvent = Find< EventsArray, { type: 'unknown'; payload: {}; }>;// type UnknownEvent = never// 미들웨어 타입:// Arg 타입의 인자를 받아 Result 타입의 결과를 반환합니다.type Middleware<Arg, Result> = (arg: Arg) => Result;// 미들웨어 예시type Middlewares = [ Middleware< { type: "getList"; withComments: true }, { id: number; name: string; comments: string[] }[] >, Middleware<{ type: "getList" }, { id: number; name: string }[]>, Middleware< { type: "getOne"; withComments: true }, { id: number; name: string; comments: string[] } >, Middleware<{ type: "getOne" }, { id: number; name: string }>];// Target 타입과 일치하는 인자를 가진 미들웨어를 찾습니다.type FindMiddleware<Target> = Find<Middlewares, Middleware<Target, any>>;// 미들웨어의 결과 타입을 가져옵니다.type GetResult<MiddlewareInput extends Middleware<any, any>> = MiddlewareInput extends Middleware<any, infer Result> ? Result : never;// 모든 조각을 하나로 합칩니다.type GetMiddlewareResult<Arg> = GetResult<FindMiddleware<Arg>>;type ListResult = GetMiddlewareResult<{ type: "getList" }>;// type ListResult = { id: number; name: string; }[]type OneResult = GetMiddlewareResult<{ type: "getOne" }>;// type OneResult = { id: number; name: string; }type ListWithCommentsResult = GetMiddlewareResult<{ type: "getList"; withComments: true;}>;// type ListWithCommentsResult = {// id: number;// name: string;// comments: string[]// }[]type OneWithCommentsResult = GetMiddlewareResult<{ type: "getOne"; withComments: true;}>;// type OneWithCommentsResult = {// id: number;// name: string;// comments: string[]// }type HelloWorld<Greeted extends string = 'world'> = `Hello ${Greeted}`type DefaultResult = HelloWorld; // 'Hello world'type Result = HelloWorld<'TypeScript'>; // 'Hello TypeScript'infer 키워드와 재귀를 사용하여 문자열에서 모든 공백을 제거하는 것과 같은 더 복잡한 문자열 조작도 수행할 수 있습니다.type RemoveWhitespace<S extends string> = S extends `${infer First} ${infer Rest}` ? `${First}${RemoveWhitespace<Rest>}` : S;type Result = RemoveWhitespace<'Hello World !'>;// type Result = "HelloWorld!"type Getter<Key extends string> = `get${Capitalize<Key>}`;type Result = Getter<'name'>;// type Result = "getName"type Result2 = Getter<'firstName'>;// type Result2 = "getFirstName"참고:Capitalize는 문자열 타입의 첫 글자를 대문자로 바꿔주는 내장 유틸리티 타입입니다.Uppercase와 재귀를 사용하여 직접 구현할 수도 있습니다.
type Capitalize< S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : S;// Uppercase 역시 유틸리티 타입이지만,// 이는 컴파일러 내부의 마법(intrinsic magic)으로 구현되어 있습니다.// 사용 예시type Result = Capitalize<"hello">;// type Result = "Hello"keyof 연산자를 기반으로 합니다.Type[Key] 구문을 통해 객체 타입의 속성 타입을 가져올 수 있습니다.type User = { id: number; name: string; email: string };type UserId = User['id'];// type UserId = numbertype UserName = User['name'];// type UserName = stringtype UserIdOrName = User['id' | 'name'];// type UserIdOrName = number | stringkeyof 연산자를 사용하면 타입의 모든 키에 대한 유니온을 가져올 수 있습니다.type User = { id: number; name: string; email: string };type UserKeys = keyof User;// type UserKeys = "id" | "name" | "email"in 키워드를 추가하여 타입의 모든 키를 반복할 수 있습니다.type UserPropertiesAsString = { // 여기서 K는 User의 각 키입니다. // 이를 맵핑하여 속성의 타입(User[K])을 // 반환하는 함수를 생성합니다. [K in keyof User]: () => User[K];};// type UserPropertiesAsString = {// id: () => number;// name: () => string;// email: () => string;// }type Promisify<R extends Record<string, (...args: any) => any>> = { [K in keyof R]: (...args: Parameters<R[K]>) => Promise<ReturnType<R[K]>>;};type Input = { getName: () => string; getById: (id: string) => { id: string; name: string };};type PromisifiedInput = Promisify<Input>;// type PromisifiedInput = {// getName: () => Promise<string>;// getById: (id: string) => Promise<{// id: string;// name: string;// }>;// }getByFilterType 메서드를 생성해 보겠습니다.type Product = { id: number; name: string; quantity: number; inStock: boolean;};type CrudWithFilters< Resource extends { id: string | number }, FilterKeys extends keyof Resource> = { // 각 필터 키에 대한 getByFilter [K in FilterKeys as `getBy${Capitalize<string & K>}`]: (value: Resource[K]) => Resource[];}type ProductCrudWithFilters = CrudWithFilters< Product, 'name' | 'quantity' | 'inStock'>;// type ProductCrudWithFilters = {// getByName: (value: string) => Product[];// getByQuantity: (value: number) => Product[];// getByInStock: (value: boolean) => Product[];// }// 실제 함수를 사용한 예시const productCrud: CrudWithFilters< Product, 'name' | 'quantity' | 'inStock'> = { getByName: (name: string) => [ { id: 1, name, quantity: 10, inStock: true } ], getByQuantity: (quantity: number) => [ { id: 2, name: 'Product 2', quantity, inStock: false } ], getByInStock: (inStock: boolean) => [ { id: 3, name: 'Product 3', quantity: 5, inStock } ],};// productCrud의 타입이 올바르게 추론되며,// 함수 구현이 기대되는 타입과 일치하지 않으면// 타입 오류가 발생합니다.const erroredProductCrud: CrudWithFilters< Product, 'name' | 'quantity' | 'inStock'> = { getByName: (name: number) => [ { id: 1, name: 'Product 1', quantity: 10, inStock: true } ], // 타입 // '(name: number) => { // id: number; // name: string; // quantity: number; // inStock: true; // }[]' // 은 '(value: string) => Product[]' 타입에 할당할 수 없습니다. // 'name'과 'value' 매개변수의 타입이 호환되지 않습니다. // 'string' 타입은 'number' 타입에 할당할 수 없습니다. getByQuantity: (quantity: string) => [{ id: 2, name: 'Product 2', quantity: 5, inStock: false }], // 타입 // '(quantity: string) => { // id: number; // name: string; // quantity: number; // inStock: false; // }[]' // 은 '(value: number) => Product[]' 타입에 할당할 수 없습니다. // 'quantity'와 'value' 매개변수의 타입이 호환되지 않습니다. // 'number' 타입은 'string' 타입에 할당할 수 없습니다. getByInStock: (inStock: boolean) => 'not found', // 타입 // '(inStock: boolean) => string' // 은 '(value: boolean) => Product[]' 타입에 할당할 수 없습니다. // 'string' 타입은 'Product[]' 타입에 할당할 수 없습니다.};// 먼저 헬퍼를 정의해 봅시다:// ObjToFunc는 함수들로 이루어진 임의의 객체를 받아// 이를 단일 함수로 변환합니다.type ObjectToFunc<Obj extends Record<string, (...args: any) => any>> = { // 객체 인자의 키를 맵핑하고 // 각 키에 대해 함수를 정의합니다. <K extends keyof Obj>(key: K, ...args: Parameters<Obj[K]>): ReturnType< Obj[K] >;};// 결과적으로 각 객체 키에 대한 함수의 유니온 타입이 생성됩니다.type Example = ObjectToFunc< { getStringLength: (s: string) => number; isEven: (n: number) => boolean; }>// type Example =// | (key: getStringLength, s: string) => number// | (key: isEven, n: number) => boolean;// 이제 실제 함수를 만들어 봅시다.const transformObjectToFunction = < Obj extends Record<string, (...args: any) => any>>( obj: Obj): ObjectToFunc<Obj> => { return ((key: string, ...args: unknown[]) => { const func = obj[key]; return func(...args); }) as ObjectToFunc<Obj>;};// 사용 예시: 서로 다른 리소스에 대한 getList 함수 모음을// 단일 getList 함수로 결합합니다.const getList = transformObjectToFunction({ authors: (filter: { name?: string }) => [ { id: 1, name: "Author 1" }, { id: 2, name: "Author 2" }, ], posts: (filter: { authorId?: number; published?: boolean }) => [ { id: 1, title: "A post" }, ], comments: (filter: { postId?: number; authorId?: number }) => [ { id: 1, content: "A comment" }, ],});getList("posts", { authorId: 1 });// 오류 없음// 다음과 같이 추론됨:// const getList: <"posts">(key: "posts", filter: {// authorId?: number;// published?: boolean;// }) => {// id: number;// title: string;// }[]getList("posts", { name: 'Author 1' });// 오류: 객체 리터럴은 알려진 속성만 지정할 수 있으며,// name은 다음 타입에 존재하지 않습니다:// { authorId?: number | undefined; published?: boolean | undefined; }getList("authors", { name: "Author 1" });// 오류 없음// 다음과 같이 추론됨:// const getList: <"authors">(key: "authors", filter: {// name?: string | undefined;// }) => {// id: number;// name: string;// }[]참고: 이전 예제에서는 TypeScript에서 제공하는 유틸리티 타입인ReturnType과Parameters를 사용했습니다.ReturnType은 주어진 함수 타입이 반환하는 값의 타입을 반환합니다. 궁금하시다면 다음과 같이 직접 구현할 수 있습니다:
type ReturnType<T extends (...args: any) => any> = T extends ( ...args: any) => infer R ? R : any;Parameters는 주어진 함수 타입의 인자들을 반환합니다.
다음과 같이 직접 구현할 수 있습니다:type Parameters<T extends (...args: any) => any> = T extends ( ...args: infer P) => any ? P : never;extends ? :)은 분기 로직을 가능하게 합니다.infer 키워드는 변수 할당처럼 작동하며 구조 분해처럼 다른 타입에서 값을 추출할 수 있게 해줍니다.아직 댓글이 없습니다.
첫 번째 댓글을 작성해보세요!

Parcel을 이용한 React Server Components
Inkyu Oh • Front-End

fate
Inkyu Oh • Front-End

at:// 프로토콜을 아시나요?
Inkyu Oh • Back-End

React Compiler의 소리 없는 실패 (그리고 해결 방법)
Inkyu Oh • Front-End