๋ฐ˜์‘ํ˜•

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ infer๋Š” ํƒ€์ž…์„ ์ถ”๋ก ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํ‚ค์›Œ๋“œ๋‹ค. ๋ฐฐ์—ด ์š”์†Œ ํƒ€์ž…, ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…, ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ ํƒ€์ž… ๋“ฑ ํŠน์ • ๋ฌธ๋งฅ์˜ ํƒ€์ž… ์ถ”์ถœ์ด ํ•„์š”ํ•  ๋•Œ infer๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ ๋ฌธ๋ฒ• : T extends infer U ? U : TU๊ฐ€ ์ถ”๋ก  ๊ฐ€๋Šฅํ•œ ํƒ€์ž…์ด๋ฉด U ๋ฆฌํ„ด, ์•„๋‹ˆ๋ฉด T ๋ฆฌํ„ด

 

infer๋Š” ํƒ€์ž… ๊ตฌํ˜„ ์˜์—ญ(ํ• ๋‹น ์—ฐ์‚ฐ์ž ์šฐ์ธก)์˜ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…(extends ์ ˆ)์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, TS ์—”์ง„์ด ํƒ€์ž…์„ ์ถ”๋ก ํ•˜๊ณ  ๋ณ€์ˆ˜(์ œ๋„ค๋ฆญ ํƒ€์ž…)์— ํ• ๋‹นํ•œ๋‹ค. ํ• ๋‹น๋œ ๋ณ€์ˆ˜๋Š” ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์˜ true ๋ถ„๊ธฐ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

// Error! 'infer' declarations are only permitted in the 'extends' clause of a conditional type.(ts1338)
type Wrong1<T extends (infer U)[]> = T[0]; // ์ œ์•ฝ ์กฐ๊ฑด extends์— ์‚ฌ์šฉํ•ด์„œ ์—๋Ÿฌ
type Wrong2<T> = (infer U)[] extends T ? U : T; // extends ์กฐ๊ฑด์ ˆ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์„œ ์—๋Ÿฌ

// Error! Cannot find name 'U'.(ts2304)
type Wrong3<T> = T extends (infer U)[] ? T : U; // false ๋ถ„๊ธฐ์— ์‚ฌ์šฉํ•ด์„œ ์—๋Ÿฌ

 

UnpackedArray


๋ฐฐ์—ด ์š”์†Œ ํƒ€์ž… ์ถ”์ถœ

 

T๊ฐ€ ๋ฐฐ์—ด์ด๊ณ , ๋ฐฐ์—ด ์š”์†Œ์˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด U(์ œ๋„ค๋ฆญ ํƒ€์ž…) ๋ณ€์ˆ˜์— ํ• ๋‹นํ•œ๋‹ค. โ–ผ

type UnpackedArray<T> = T extends (infer U)[] ? U : T;
type T0 = string[];
type U0 = UnpackedArray<T0>; // string

 

UnpackedArray ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž… ์‹คํ–‰ ํ๋ฆ„ via Bytefer

 

UnpackedFn


ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ ํƒ€์ž… ์ถ”์ถœ

 

T๊ฐ€ ํ•จ์ˆ˜์ด๊ณ , ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋ณ€์ˆ˜(์ œ๋„ค๋ฆญ ํƒ€์ž…) U์— ํ• ๋‹นํ•œ๋‹ค. โ–ผ

type UnpackedFn<T> = T extends (...args: unknown[]) => infer U ? U : T;
type T1 = () => string;
type U1 = UnpackedFn<T1>; // string

 

ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋กœ๋“œํ–ˆ๋‹ค๋ฉด ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ํƒ€์ž… ์ถ”๋ก ์— ์‚ฌ์šฉํ•œ๋‹ค. ์ฐธ๊ณ ๋กœ ์‹œ๊ทธ๋‹ˆ์ฒ˜(Signiture)๋Š” ํ•จ์ˆ˜๋‚˜ ๋ฉ”์„œ๋“œ์˜ ์ž…๋ ฅ(ํŒŒ๋ผ๋ฏธํ„ฐ)๊ณผ ์ถœ๋ ฅ(๋ฐ˜ํ™˜๊ฐ’)์— ๋Œ€ํ•œ ํƒ€์ž… ์ •๋ณด๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;

type U2 = UnpackedFn<typeof foo>; // string | number

 

ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋กœ๋“œ ํ–ˆ์„ ๋•Œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ via Bytefer

 

Unpacked


๋ฐฐ์—ด ์š”์†Œ ํƒ€์ž… / ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ ํƒ€์ž… / ํ”„๋กœ๋ฏธ์Šค ๊ฒฐ๊ณผ ํƒ€์ž… ์ถ”์ถœ

 

T๊ฐ€ ๋ฐฐ์—ด | ํ•จ์ˆ˜ | ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ์•„๋‹ˆ๋ฉด T๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. โ–ผ

type Unpacked<T> =
    T extends (infer U)[] ? 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>[]>;  // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string

 

PropertyType


๊ฐ์ฒด ํ‚ค ํƒ€์ž… ์ถ”์ถœ

 

T๊ฐ€ ๊ฐ์ฒด์ด๊ณ , ๊ฐ์ฒด ํ‚ค ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋ณ€์ˆ˜(์ œ๋„ค๋ฆญ ํƒ€์ž…) U, R์— ํ• ๋‹นํ•œ๋‹ค. โ–ผ

type User = { id: number; name: string };

type PropertyType<T> = T extends { id: infer U; name: infer R } ? [U, R] : T;
type U3 = PropertyType<User>; // [number, string]

 

๋งŒ์•ฝ ์ œ๋„ค๋ฆญ ํƒ€์ž… U๋ฅผ 1๊ฐœ๋งŒ ์„ ์–ธํ•˜๋ฉด ์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ณต๋ณ€ ์œ„์น˜(ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ œ์™ธํ•œ ๊ณณ)์—์„œ ๋™์ผํ•œ ํƒ€์ž… ๋ณ€์ˆ˜(U)์— ๋Œ€ํ•œ ํ›„๋ณด๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ๋ฉด ์ตœ์ข… ํƒ€์ž…์„ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์œผ๋กœ ์œ ์ถ”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

type PropertyType<T> = T extends { id: infer U; name: infer U } ? U : T;
type U4 = PropertyType<User>; // string | number

 

๋ฐ˜๋ฉด, ๋ฐ˜๊ณต๋ณ€ ์œ„์น˜(ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ)์—์„œ ๋™์ผํ•œ ํƒ€์ž… ๋ณ€์ˆ˜(U)์— ๋Œ€ํ•œ ํ›„๋ณด๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ๋ฉด ์ตœ์ข… ํƒ€์ž…์„ ์ธํ„ฐ์„น์…˜ ํƒ€์ž…์œผ๋กœ ์œ ์ถ”ํ•œ๋‹ค. ๋•Œ๋ฌธ์— ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ U๋Š” string & number ํƒ€์ž…์ด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ œ๋„ค๋ฆญ์œผ๋กœ ๋„˜๊ธด ๋‘ ๋ฉ”์„œ๋“œ์˜ x ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…๊ณผ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— false ๋ถ„๊ธฐ๋ฅผ ํƒ€์„œ never๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. (T extends U ? X : Y ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ๋ฌธ๋ฒ•์—์„œ T๋ฅผ U์— ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์œผ๋ฉด true ๋ถ„๊ธฐ๋ฅผ, ํ• ๋‹นํ•  ์ˆ˜ ์—†์œผ๋ฉด false ๋ถ„๊ธฐ๋ฅผ ํƒ„๋‹ค)

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U
  : never;
  
type U5 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // never
๋”๋ณด๊ธฐ

๋” ์ž์„ธํ•œ ๋‚ด์šฉ์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ๊ณต๋ณ€์„ฑ / ๋ฐ˜๊ณต๋ณ€์„ฑ / ์ด๋ณ€์„ฑ ํฌ์ŠคํŒ… ์ฐธ๊ณ 

 

  • ๊ณต๋ณ€์„ฑ : ์Šˆํผ ํƒ€์ž…(๋„“์€ ํƒ€์ž…)์ด ์„œ๋ธŒ ํƒ€์ž…(์ข์€ ํƒ€์ž…)์„ ์ปค๋ฒ„ํ•˜๋Š” ๊ด€๊ณ„
    • ์„œ๋ธŒ ํƒ€์ž…์„ ์Šˆํผ ํƒ€์ž…์— ๋Œ€์ž…ํ•  ์ˆ˜ ์žˆ์Œ e.g. superType = subType
    • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ณธ ๋™์ž‘
  • ๋ฐ˜๊ณต๋ณ€์„ฑ : ์„œ๋ธŒ ํƒ€์ž…(์ข์€ ํƒ€์ž…)์ด ์Šˆํผ ํƒ€์ž…(๋„“์€ ํƒ€์ž…)์„ ์ปค๋ฒ„ํ•˜๋Š” ๊ด€๊ณ„ 
    • ์Šˆํผ ํƒ€์ž…์„ ์„œ๋ธŒ ํƒ€์ž…์— ๋Œ€์ž…ํ•  ์ˆ˜ ์žˆ์Œ e.g. subType = superType
    • ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๋™์ž‘
  • ์ด๋ณ€์„ฑ : ๊ณต๋ณ€์„ฑ๊ณผ ๋ฐ˜๊ณต๋ณ€์„ฑ์„ ๋ชจ๋‘ ํฌํ•จํ•˜๋Š” ๊ด€๊ณ„ 
    • ์Šˆํผ ํƒ€์ž…๊ณผ ์„œ๋ธŒ ํƒ€์ž…์„ ๋ชจ๋‘ ๋Œ€์ž…ํ•  ์ˆ˜ ์žˆ์Œ
    • strictFunctionTypes ์˜ต์…˜์„ ๊ป์„ ๋•Œ ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๋™์ž‘

 

FirstIfString


๋ฐฐ์—ด์˜ ์ฒซ๋ฒˆ์งธ ์š”์†Œ๊ฐ€ ๋ฌธ์ž์—ด ํƒ€์ž…์ด๋ฉด ์ถ”์ถœ

 

์•„๋ž˜ ์˜ˆ์‹œ์˜ ์ฒซ๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์—์„  ์ œ๋„ค๋ฆญ์œผ๋กœ ๋ฐ›์€ T๊ฐ€ ๋ฐฐ์—ด์ธ์ง€ ํ™•์ธํ•˜๊ณ , ๋ฐฐ์—ด์˜ ์ฒซ๋ฒˆ์งธ ์š”์†Œ๋ฅผ ํƒ€์ž… ๋ณ€์ˆ˜ S์— ํ• ๋‹นํ•œ๋‹ค. ๋‘๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์—์„  ํ• ๋‹น๋œ ํƒ€์ž… ๋ณ€์ˆ˜ S๊ฐ€ ๋ฌธ์ž์—ด์ธ์ง€ ํ™•์ธํ•˜๊ณ  ์ฐธ์ด๋ฉด S๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

type FirstIfString<T> = T extends [infer S, ...unknown[]] // ์ฒซ๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…
  ? S extends string // ๋‘๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…
    ? S
    : never
  : never;

type A = FirstIfString<[string, number, number]>; // string
type B = FirstIfString<['a', number, number]>; // 'a'
type C = FirstIfString<['a | b', number]>; // 'a' | 'b'

 

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ 4.7 ๋ฒ„์ „๋ถ€ํ„ฐ infer ํƒ€์ž…์— extends ์ ˆ์„ ์‚ฌ์šฉํ•ด์„œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„  2๊ฐœ์˜ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด 1๊ฐœ์˜ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…๋งŒ ์‚ฌ์šฉํ•ด์„œ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

// infer์™€ extends๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํƒ€์ž… ๋ณ€์ˆ˜ S๋ฅผ ๋ฌธ์ž์—ด ํƒ€์ž…์œผ๋กœ ์ œํ•œํ•˜๊ณ  ์žˆ๋‹ค
type FirstIfString<T> = T extends [infer S extends string, ...unknown[]]
  ? S
  : never;

 

UnionToIntersection


์œ ๋‹ˆ์˜จ ํƒ€์ž…์„ ๋ฐ›์•„ ์ธํ„ฐ์„น์…˜ ํƒ€์ž…์œผ๋กœ ์ถ”์ถœ

 

U ํƒ€์ž… ๊ฐ ๋ฉค๋ฒ„์˜ ํ•จ์ˆ˜ ํƒ€์ž…์„ ์ƒ์„ฑํ•˜๊ณ , ๊ฐ ํ•จ์ˆ˜ ํƒ€์ž…์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๋ก ํ•˜์—ฌ ์ธํ„ฐ์„น์…˜ ํƒ€์ž…์„ ์ƒ์„ฑํ•˜๋Š” ์˜ˆ์‹œ. โ–ผ

type UnionToIntersection<U> = (
  U extends any ? (arg: U) => void : never // ์ฒซ๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…
) extends (arg: infer R) => void // ๋‘๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…
  ? R
  : never;

type T6 = { a: 'a' } | { b: 'b' };
type T7 = UnionToIntersection<T6>; // { a: 'a' } & { b: 'b' }

 

  1. ์ฒซ๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž… : U extends any ? (arg: U) => void : never
    • ํƒ€์ž… ๋ณ€์ˆ˜ U๊ฐ€ ์–ด๋–ค ํƒ€์ž…์ด๋“  ์ƒ๊ด€์—†์ด (arg: U) => void ํƒ€์ž…์œผ๋กœ ํ™•์žฅ
    • ํƒ€์ž… ๋ณ€์ˆ˜ U์˜ ๊ฐ ๋ฉค๋ฒ„์— ๋Œ€ํ•ด (arg: U) => void ํƒ€์ž…์„ ๊ฐ€์ง„ ์œ ๋‹ˆ์˜จ ๋ฐ˜ํ™˜
      (arg: { a: "a" }) => void | (arg: { b: "b" }) => void
  2. ๋‘๋ฒˆ์งธ ์กฐ๊ฑด๋ถ€ ํƒ€์ž… : (...) extends (arg: infer R) => void ? R : never
    • ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… R ์ถ”๋ก 
    • ๋ฐ˜๊ณต๋ณ€ ์œ„์น˜์—์„œ ํƒ€์ž… ๋ณ€์ˆ˜(R)์˜ ํ›„๋ณด๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ด๋ฏ€๋กœ ์ธํ„ฐ์„น์…˜ ํƒ€์ž… ๋ฐ˜ํ™˜

 

๋ ˆํผ๋Ÿฐ์Šค


 

Using TypeScript infer Like a Pro

Explained with animations. Master TypeScript Conditional Types and infer type inference.

levelup.gitconnected.com

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•