
infer, never๋ง ๋ณด๋ฉด ๋๋ ค์์ง๋ ๋น์ ์ ์ํ ํ์ ์ถ๋ก - ์์ฉ ๋ฌธ์
infer, never๋ง ๋ณด๋ฉด ๋๋ ค์์ง๋ ๋น์ ์ ์ํ ํ์ ์ถ๋ก - ์์ฉ ๋ฌธ์ ๊ด๋ จ
'infer, never๋ง ๋ณด๋ฉด ๋๋ ค์์ง๋ ๋น์ ์ ์ํ ํ์ ์ถ๋ก ' ๊ด๋ จ ์์์ ๋งํฌ์์ ๋ณด์ค ์ ์์ต๋๋ค.
์์์ ๋ฐฐ์ด ๋ด์ฉ์ ํ ๋๋ก ์ด๋ ค์ด ํ์ ๋ฌธ์ ๋ฅผ ํ๋ ํ์ด ๋ณด๊ฒ ์ต๋๋ค.
์, ์ฌ๊ธฐ ์์ ์ฌ๊ท ํจ์๊ฐ ์์ต๋๋ค.
function flattenObject(obj: any, result: any = {}): any {
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] && !(obj[key] instanceof Array)) {
flattenObject(obj[key], result)
} else {
result[key] = obj[key]
}
}
return result
}
ํจ์ ๊ตฌํ๋ถ๋ ์ ๊ฒฝ์ฐ์ง ๋ง์๊ณ , ๋์๋ง ํ ๋ฒ ๋ณผ๊น์?
flattenObject({
x: 0,
y: 'babo',
z: ['hi'],
a: {
b: {
c: null,
},
d: undefined,
}
})
//
// { x: 0, y: 'babo', z: ['hi'], c: null, d: undefined }
๋ค, ๊ทธ๋ ์ต๋๋ค. ์ด ํจ์๋ ์ค์ฒฉ๋ ๊ฐ์ฒด๋ฅผ ํํํ๊ฒ ํด๋ ํจ์์ ๋๋ค. ์ด ํจ์์ ํ์ ์ ๋ฌ์๋ด ์๋ค. ๋จ, ์๋์ ์ ์ ์กฐ๊ฑด์ด ๋ถ์ต๋๋ค.
- ๋ชจ๋ ์์ฑ์ ์ด๋ฆ์ ์ ์ผ์ฑ์ ๋ง์กฑํ๋ค(=๊ฒน์น์ง ์๋๋ค).
- ๋ฐฐ์ด์ ํผ์น์ง ์๋๋ค.
null
์ด๋undefined
๊ฐ์ ๊ทธ๋๋ก ์ ์ง๋์ด์ผ ํ๋ค.
ํํํ ๊ฐ์ฒด์ ์ค์ฒฉ๋ ๊ฐ์ฒด ๋ถ๋ฆฌํ๊ธฐ
์ ์ผ ๋จผ์ ์๊ฐ๋๋ ์ ๊ทผ์ '์ค์ฒฉ๋๋ ์์ฑ'๊ณผ '์ค์ฒฉ๋์ง ์๋ ์์ฑ'์ ์ชผ๊ฐ์ ์ฒ๋ฆฌํ ๋ค, ์กฐ๋ฆฝํ๋ ๊ฒ์ ๋๋ค.
๋จผ์ , ์ค์ฒฉ๋์ง ์์ ์์ฑ๋ถํฐ ์ถ์ถํด ๋ด
์๋ค. T์ undefined
๋ never
, unknown
๊ฐ์ ์ด์ํ ํ์
์ ๊ณ ๋ คํ์ง ์๊ธฐ ์ํด extends object
๋ก ๋ฐฉ์ดํฉ๋๋ค. ๊ทธ ๋ค ์ง์ฐ๊ณ ์ถ์ ๊ฐ์ธ ๊ฒฝ์ฐ์ never
๋ก ์ฒ๋ฆฌํด ๋ฒ๋ฆฌ๋ฉด ๋์ง ์์๊น์?
type SimpleFlattendObject<T extends object> = {
[K in keyof T]: FilterValueType<T[K]>
}
type FilterValueType<T> = T extends object
? T extends null | unknown[]
? T
: never
: T
ํ ๋ฒ ํ ์คํธํด ๋ณผ๊น์?
const test: SimpleFlattendObject<{
x: number,
y: {
z: []
}
}> = {
x: 0,
}
// Property 'y' is missing in type '{ x: number; }' but required in type `FlattenKeys<{ ... }>`
์๋ํ ๋๋ก ๋์ํ์ง ์์ต๋๋ค. ๊ฐ์ฒด ๋ฆฌํฐ๋ด์์ ์๋ ์์ฑ์ ํ์ธํด ๋ณด๋, y
๊ฐ never
๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. y
๊ฐ test
๊ฐ์ฒด์ ์์ด์ผ ๋๋ ๊ฑด ๋ง์ง๋ง, ๊ฐ์ด never
ํ์
์ด๋ผ๊ณ ํด์ ํค๊น์ง ์๋์ผ๋ก ์ง์ฐ์ง ์์ต๋๋ค. ๋ฐ๋ผ์, ๊ฐ์ด ์๋ ์์ฑ ํค๋ฅผ ์์ฒ์ ์ผ๋ก ์ง์์ผ ํฉ๋๋ค.
type SimpleFlattendObject<T extends {}> = {
[K in FilterPrimitiveKeys<T, keyof T>]: T[K]
}
type FilterPrimitiveKeys<T, K> = K extends keyof T
// ๋ชจ๋ ๋ฐฐ์ด์ ํ์ฉํ๊ธฐ ์ํด ๊ฐ์ฅ ํฐ ํ์
์ธ unknown ํ ๋น
? T[K] extends unknown[]
// ์กฐ๊ฑด์ ๋ง์กฑํ์ ๋ ํค๋ฅผ ๋ฐํ. ์ผ์ข
์ early return
? K
: T[K] extends object
? never
: K
: never
ํ ์คํธ๋ฅผ ํด ๋ณผ๊น์?
type X = SimpleFlattendObject<{
x: number,
y: {
z: string,
},
a: null,
b: [1],
}>
//
// {
// x: number,
// a: null,
// b: [1]
// }
์ด์ ์ ๋๋ค์! ์ด์ ์ค์ฒฉ๋ ๊ฐ์ฒด๋ง ์ ์ฒ๋ฆฌํ๋ฉด ๋๊ฒ ๊ตฐ์. ์ค์ฒฉ๋์ง ์์ ๊ฐ์ ์ฒ๋ฆฌํ ์ฝ๋๋ฅผ ์์ ํ ๋ฐ์ ์ํค๋ฉด ๋๊ฒ ๋ค์.
type NestedObject<T extends object> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends unknown[]
? never // ์๊น๋ ์ ๋ฐ๋!
: T[K] extends object
? K
: never
: never
ํ ์คํธํด ๋ณผ๊น์?
// ์์
type X = NestedObject<{
x: number
y: { z: number }
a: { b: { c: [] }}
d: undefined
}>
//
// {
// y: { z: number }
// a: { b: { c: [] }}
// }
์ข์ต๋๋ค.
์ค์ฒฉ๋ ๊ฐ์ฒด ํ ๋จ๊ณ ๋ค์ด์ฌ๋ฆฌ๊ธฐ
์ด์ y ์์ ๋ค์ด์๋ z, a ์์ ๋ค์ด์๋ b๋ฅผ ํ ๋จ๊ณ ์ฌ๋ฆฌ๊ณ ์ถ์ต๋๋ค. ์ผ๋จ ์ฌ๊ท์ ์ธ ๊ฑด ์๊ฐํ์ง ๋ง๊ณ , ํ ๋จ๊ณ๋ง ์ฌ๋ ค๋ด ์๋ค.
์ฐ์ ์ ์์ ์๋ ๊ฐ์ 'ํค'๋ ๋ ์ด์ ์ค์ํ์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ์ ํ์ ๋ง ๋ชจ์กฐ๋ฆฌ ์ถ์ถํฉ์๋ค.
type Values<T extends object> = T[keyof T]
type X = Values<{ a: string, b: number }> // string | number
์๊น ๋ง๋ค์๋ NestedObject
๋ฅผ ์ฌ๊ธฐ์ ๋ฃ์ด๋ด
์๋ค.
type UnwrappedObject<T extends object> = Values<NestedObject<T>>
ํ ์คํธํด ๋ด ์๋ค.
type X = UnwrappedObject<{
x: number
y: { z: number }
a: { b: { c: [] }}
d: undefined
}>
//
// {
// z: number
// } | {
// b: { c: [] }
// }
์๋ํ ๊ฒ๊ณผ ๋น์ทํด์ก์ต๋๋ค. |
๋ฅผ &
๋ก ๋ฐ๊พธ๊ธฐ๋ง ํ๋ฉด ์ํ๋ ํ์
์ด ๋ ๊ฒ ๊ฐ๋ค์. ๊ทธ๋ฐ๋ฐ |
๋ฅผ ์ด๋ป๊ฒ &
๋ก ๋ฐ๊พธ์ฃ ?
ํฉ์งํฉ์ ๊ต์งํฉ์ผ๋ก
Stack Overflow์ ์๋ ํ ์ฌ์ผ์ ๊ณ ์๋ ์๋์ ๊ฐ์ ํต์ ์ ์ํ์ต๋๋ค(์ถ์ฒ: Transform union type to intersection type).
type ToIntersection<T> = (
T extends any
? (_: T) => void
: never
) extends (_: infer S) => void
? S
: never
์๊ธด ๊ฒ ์๋นํ ๋นํฉ์ค๋ฝ์ง๋ง, ์ฐจ๊ทผ์ฐจ๊ทผ ํ๋์ฉ ๋ถ์ํด ๋ด
์๋ค. ๋จผ์ ์ฒซ ๋ฒ์งธ ๊ดํธ๋ฅผ ์์๋ก F<T>
๋ผ๊ณ ํฉ์๋ค.
F<T> = T extends any ? (_: T) => void : never
์ด ํ์
ํํ์์ T
๋ฅผ (_: T) => void
๋ผ๋ ํจ์ ํ์
์ผ๋ก ๋ฐ๊ฟ๋๋ค. ์ด๋ ๋ถ๋ฐฐ ๋ฒ์น์ ์ ์ฉํ๊ธฐ ์ํด์ T extends any
๋ฅผ ์จ ์ค ๊ฒ์
๋๋ค. ์ฆ, ์๋์ ๊ฐ์ ์ผ์ด ๋ฒ์ด์ง๋๋ค.
type X = F<A | B>
// F = ((x: A) => void) | ((x: B) => void)
์ด์ ๋๋จธ์ง ๋ฐ๊นฅ์ ์กฐ๊ฑด๋ถ ํ์
์ ํด์ํ๋ฉด ๋ฉ๋๋ค. ์ด๋ค ํจ์์ ๋์ด์ด ์๊ณ , ๊ทธ ํจ์๋ฅผ ๋ชจ๋ ํฌ๊ดํ๋ ํจ์์ ์ธ์๋ฅผ infer
๋ก ์ถ๋ก ํ๋ค์!
type ToIntersection<T> = (...) extends (_: infer S) => void ? S : never
๊ทธ๋ฐ๋ฐ ํจ์์ ์ธ์ ํ์
์ ๋ฐ๊ณต๋ณ์ฑ ๋๋ฌธ์ ๋ฐฉํฅ์ด ๊ฑฐ๊พธ๋ก๋ผ๊ณ ํ์ฃ ? ๋ฐ๋ผ์ ์ธ์์ ํ์
์ ๋ ์์์ ธ์ผ ํฉ๋๋ค. A
์ ์๋ธํ์
์ด๋ฉด์ ๋์์ B
์ ์๋ธํ์
์ธ, ๊ฐ์ฅ ๋์ ํ์
์ด ํ์ํฉ๋๋ค. ์งํฉ๋ก ์ ๋ฐ๋ฅด๋ฉด ์ด๋ฅผ ๋ง์กฑํ๋ ํ์
์ A & B
์
๋๋ค.
์๊ธธ๋ก ์ข ์์ง๋ง ์ด๊ฑธ UnwrappedObject
์ ์ ์ฉํด ๋ด
์๋ค.
type UnwrappedObject<T extends object> = ToIntersection<Values<NestedObject<T>>>
type X = UnwrappedObject<{
x: number
y: { z: number }
a: { b: { c: [] }}
d: undefined
}>
/*
{
z: number
} & {
b: { c: [] }
}
*/
์๋ํ ๋๋ก ๋์ํ๋ค์! ๊ทธ๋ฌ๋ฉด ์๊น ๋จ์ํ๊ฒ ํ์ดํค์น ๊ฑธ ๊ฐ์ด ํฉ์ณ์ฃผ๋ฉด, ์ค์ฒฉ๋ ๊ฐ์ฒด ํ ๋จ๊ณ ๋ค์ด์ฌ๋ฆฌ๊ธฐ๋ ๋๋ฉ๋๋ค.
type FlattendObject<T extends object> = SimpleFlattendObject<T> & UnwrappedObject<T>
์ง์ฐ ํ๊ฐ๋ฅผ ํ์ฉํ์ฌ ์ฌ๊ท์ ์ผ๋ก ์ํํ๊ธฐ
์ด์ ๋ง์ง๋ง ๋๊ด์ด ๋จ์์ต๋๋ค. ๋ฐ๋ก ์ถ๊ฐ์ ์ธ ์ค์ฒฉ์ ๋ํด์ ์ฌ๊ท์ ์ผ๋ก ์ํํ๋ ๊ฒ์ ๋๋ค.
๋ค์ค ์ค์ฒฉ๋ ๊ฐ์ฒด๋ NestedObject
์ ๊ฐ ์์ ๋ค์ด์์ต๋๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ ๋ค๋ฆญ์ธ FlattendObject
๋ ํ ๋จ๊ณ์ ๋ํด์ ์ด๋ฅผ ์ํํ์ผ๋ ์ด๊ฑธ ์ง์ด๋ฃ์ผ๋ฉด ๋์ง ์์๊น์? Values
๋ก ํ์ดํค์น ๋ค์, ๊ต์งํฉ์ผ๋ก ๋ณํํ๊ธฐ ์ ์ ์ฌ๊ท์ ์ผ๋ก ์ํํ๋ฉด ๋๊ฒ ๊ตฐ์!
type UnwrappedObject<T extends object> = ToIntersection<FlattendObject<Values<NestedObject<T>>>>
์ํ๊น๊ฒ๋ ์ด๋ ๊ฒ ํ๋ฉด, FlattendObject
์ ์ ์์ FlattendObject
๊ฐ ์ฌ์ฉ๋์ด ๋ฌดํ ๋ฃจํ๊ฐ ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์กฐ๊ฑด๋ถ ํ์
์ ์ง์ฐ ํ๊ฐ ์ฑ์ง์ ์ด์ฉํ๋ฉด ์ด๋ฅผ ํ๊ฐํ ์ ์์ต๋๋ค.
type RecursionHelper<T> = T extends object ? FlattendObject<T> : never
type UnwrappedObject<T extends object> = ToIntersection<RecursionHelper<Values<NestedObject<T>>>>
์ฌ์ค T
๊ฐ ๊ฐ์ฒด๊ฐ ์๋์ง ๊ฒ์ฌ๋ฅผ ์ ํด๋ ๋๋๋ฐ, ์ด๋ฏธ Values
๊ฐ ๊ฐ์ฒด๋ง ๋ฐํํ๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ทธ๋ฐ๋ฐ tsc๋ ๋ณด์์ ์ผ๋ก ํ์
์ ์ถ๋ก ํ๋ค๊ณ ํ์ฃ ? ์ฆ, Values
๊ฐ ๋ฌด์กฐ๊ฑด ๊ฐ์ฒด๋ผ๋ ๊ฒ์ ๋ณด์ฆํ์ง ๋ชปํฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก extends object
๋ก ๋ช
์์ ์ผ๋ก ๋ฐฉ์ด๋ฅผ ํ ๊ฒ์
๋๋ค.
์, ์ด์ ํ
์คํธ๋ฅผ ํด๋ด
์๋ค. ๊ทธ๋ฐ๋ฐ ํ์
์ถ๋ก ์ ๊น์ด๊ฐ ๋๋ฌด ๊น๋ค ๋ณด๋, IDE๊ฐ ์ ๋๋ก ํ์
์ ๋ณด์ฌ์ฃผ์ง ์์ต๋๋ค. ํ์ง๋ง Matt Pocock (mattpocockuk
)์ด ๊ณ ์ํ Roll
์ด๋ผ๋ ํต์ ์ฐ๋ฉด, ๋ชป์๊ธด ํ์
์ ํผ์ณ์ ๋ณด์ฌ์ค๋ค๊ณ ํฉ๋๋ค. ์๋ง tsc์ ํํฐ ๋ด๋ถ ๊ตฌํ์ ๊ณ ๋ คํ ๊ฒ ๊ฐ์ต๋๋ค.
type Roll<T> = {
[K in keyof T]: T[K]
} & {}
type X = Roll<FlattendObject<{
a: 'a'
b: null
c: {
d: 'd',
e: {
f: 0
},
g: null
}
}>>
/*
type X = {
a: "a";
b: null;
d: "d";
g: null;
f: 0;
}
*/
์ด์ ์ด ํต์ ํจ์์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค. result
๋ ์ค๊ฐ ๊ณผ์ ์ ์ํด์ ์ฐ์ด๋ ๊ตฌํ์ ๊ด๋ จ๋ ๊ฐ์ด๋ฏ๋ก ๊ตณ์ด ์ ๋ฐํ๊ฒ ์ถ๋ก ํ ํ์๋ ์์ต๋๋ค.
function flattenObject<T extends object>(obj: T, result: any = {}): FlattendObject<T>
์ ์ฒด ์ฝ๋๋ TypeScript: TS Playground์์ ํ์ธํ ์ ์์ต๋๋ค.
๋ง์น๋ฉฐ
๊ตณ์ด ์ด๋ ๊ฒ๊น์ง ์ ๋ฐํ ํ์ ์ถ๋ก ์ ์ฌ์ฉํด์ผ๋ง ํ๋๊ฐ ํ๋ ์๋ฌธ์ ๋๋ ์ ์์ต๋๋ค. ์ ๋ ์ด๋ ์ ๋๋ ๋์ํฉ๋๋ค. ๊ณ ๊ธ ํ์ ์ถ๋ก ์ ์ง์ ์ฅ๋ฒฝ์ด ๋๊ธฐ ๋๋ฌธ์, ๋ชจ๋ ํ์์ด TS์ ํต๋ฌํ์ง ์์๋ค๋ฉด ์คํ๋ ค ํ์ ์ ๋ฐฉํดํ ์๋ ์๊ฒ ์ต๋๋ค. ์ค๋ฌด์์ ํธ๋ ๋๋ถ๋ถ์ ๋ฌธ์ ๋ ์ด๋ ๊ฒ ๋ณต์กํ์ง๋ ์๊ณ ์. ํ์ง๋ง ๊ผญ ๋๋ค์์ ์ค๋ฌด์ ์ง์ ์ ์ธ ๋์์ ์ฃผ์ง ์๋๋ค๊ณ ํด์ ์ธ๋ชจ์๋ ์ง์์ ์๋๋๋ค. ์น ๊ฐ๋ฐ์๊ฐ ์์คํ ๊ฐ๋ฐ์ ํ์ง ์์ง๋ง ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ์ ๋ํ ์ดํด๊ฐ ํ์ํ๋ฏ์ด์.
์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋ฐํ๋ ๊ฐ๋ฐ์๋ก์ ํ์ ์ ๋ํ ์ ๋ฌธ์ฑ์ ํ์๋ถ๊ฐ๊ฒฐํฉ๋๋ค. ์ค์ ๋ก ์ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ ๋์ค, ์์์ ํ์ด๋ณธ ๋ฌธ์ ๋ฅผ ๋ฅ๊ฐํ๋ ๋์ด๋์ ํ์ ์ ๋ค๋ฃฌ ์ ๋ ์์ต๋๋ค. ์ด ๋ชจ๋ ๊ฒ์ ์ง์ ์ธ ํํ์ฑ์ ์ถ๊ตฌํ๊ธฐ ์ํจ๋, ์๋ฌด๋ ์ฝ์ง ๋ชปํ๋ ๋ฌด๊ธฐ๋ฅผ ๊ฐ๋ฐํ๊ธฐ ์ํจ๋ ์๋๋๋ค. ๊ทธ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฌ์ฉ์ฑ์ ์ฆ๋์ํค๊ณ ๋ ๋์ ๋๊ตฌ๋ฅผ ๋ง๋ค๊ธฐ ์ํจ์ ๋๋ค.
infer, never๋ง ๋ณด๋ฉด ๋๋ ค์์ง๋ ๋น์ ์ ์ํ ํ์ ์ถ๋ก ์๋ฆฌ์ฆ