我不敢相信这个可恶的东西真的有效:
function factory<NestedKey extends NestedKeyOf<typeof obj>>(namespace?: NestedKey) {
return function getter<
TargetKey extends
(NestedKey extends undefined
? NestedKeyOf<typeof obj>
: NestedKeyOf<Get<typeof obj, NestedKey>>)
>(key: TargetKey): NestedKey extends undefined ? Get<typeof obj, TargetKey> : Get<typeof obj, `${NestedKey}.${TargetKey}`> {
return get(obj, [namespace, key].filter((part) => part != null).join('.'));
};
}
请允许我解释一下。首先,我在执行 Nested KeyOf 时遇到了很多麻烦:(
我必须重写它,因为它报告类型实例化太深,所以这是我的版本:
type NestedKeyOf<O> = O extends object ? {
[K in keyof O]: `${K & string}` | `${K & string}.${NestedKeyOf<O[K]>}`;
}[keyof O] : never;
它做同样的事情;只是写法不同。接下来我们需要能够“深度获取”一个属性(模仿 lodash 的 get 函数):
type Get<O, P extends string> =
P extends `${infer Key}.${infer Rest}`
? Key extends keyof O ? Get<O[Key], Rest> : never
: P extends keyof O ? O[P] : never;
额外的extends keyof O
是为了防止无效属性访问时出现错误。
最后就是我上面展示的怪物。
我们需要存储什么namespace
是,所以我们使用泛型。这个通用名称恰如其分NestedKey
我们现在可以在定义中使用它getter
.
getter
还需要一个嵌套键。然而,它的类型不同时namespace
未提供。
因此NestedKey extends undefined
有没有。如果它不存在,那么key
应该是原始对象的嵌套键。否则,它是值的嵌套键namespace
指向,使用Get
.
最后在返回值中,我们做了同样的事情。如果NestedKey
不存在,则我们深度获取目标键,否则,我们深度获取嵌套键和目标键。
操场 https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbwOYFMYF84DMoRHAcgBsIATAQwGcALAgbgFgAoZmATzBTgDkVKYUpANIo2AeSwAeMQD44AXjhi4KAB4CAdqUpwIAIwBWKAMbwA-ImZxrcANpC4wDXADWoiFiUBdAFxwABgAkCA4AZHD8UE5I6P5wAD4BwWERMFEaMQB0wbz8giLiUmL2XjKxjEzotm5sHt5wfhooAG4oUBWsHFwA4mjSADRwAAoq6ihaOpHRcvJWw6Oa2kkITlhtcAXo2Ssaa1BwAEp8GP5z1hYFC+NLNXXKFr0w0vaiXoNH-HKNLW1nDfNqRY6W6ee5KWxDLz-JqtdrMZjGCAafi6QwKSxMaxYCAQPwEbEQAj9OZ6chQPwIP7kPEACRQRBIRL+egpxjxAHVoERSAR0HM+ZU4FQ4IjkTAOkwsABXDSmYBI7DkUzQNiSXICYSiK4THjHfKiCSSdicOr6AwyGQACg05BAfDASpQZj86v1bAAlBjrFA0FKoM5pbKYPLnKgYAIoJI-gAVMlhy6A646P7WS2uzVsbVLGWkFBYJyCFM2CzpgqG40oU2GGRF6wuvUZw2PI1dKsGQal0QW91zK01PyxqDx0Tu+t5DNZnQ5vMF0hwB59CttwaD4dsL5wZtLzxmwZBBCdthbYKrtCbfxySmYmw+mB+0NoS27uw2u2UB3GFCDGpeTL5ogRpaloOrAnryHIIHwAAhIoGhSgy7qZAYEBOJaBCZAQ7ruhU1joBUAoIkiKJhhG6JYEqMAqpa2HMCRbRoXomTGJhdBwAA9GxhCclA3IEEAA
The as const
断言是为了验证它实际上是深度获得正确的值。
根据新要求进行更新。我们需要一些新类型来告诉我们哪些键是对象,哪些是字符串:
type GetObjectKeys<O, K extends string> = {
[P in K]: Get<O, P> extends string ? never : P;
}[K];
type GetStringKeys<O, K extends string> = {
[P in K]: Get<O, P> extends string ? P : never;
}[K];
它们采用对象类型和一些潜在的键。它检查每个键的类型。对于对象来说,如果它是一个字符串,那么它是never
, 否则P
。我们用never
因为下面我们将得到所有剩余键的并集[K]
and T | never
简化为T
.
然后因为namespace
是可选的,它带来了一些困难。我的一个错误是相信如果namespace
没有提供,NestedKey
将是未定义的(所以之前的答案实际上是不正确的)。稍后将对此进行更正。
为了解决可选的namespace
,我们制作原始的factory
函数的参数namespace
not可选并将其重命名为_factory
(内部/私人)。然后我们创建一个新的factory
像这样调用它的函数:
function factory<NestedKey extends GetObjectKeys<typeof obj, NestedKeyOf<typeof obj>>>(namespace?: NestedKey) {
return _factory<
{ __private: typeof obj },
GetObjectKeys<typeof obj, NestedKeyOf<typeof obj>> extends NestedKey ? "__private" : `__private.${NestedKey}`
//@ts-ignore Unfortunately I don't think there is a good way to prevent this error
>({ __private: obj }, namespace ? `__private.${namespace}` : "__private");
}
它需要任何对象键,但如果未提供名称空间,它将创建默认值__private
,因为我们用属性将目标对象包装在另一个对象中__private
避免命名空间是可选的这一事实。将其视为委托者。
现在对于修改后的_factory
功能:
function _factory<Obj extends unknown, NestedKey extends NestedKeyOf<Obj>>(obj: Obj, namespace: NestedKey) {
return function getter<
TargetKey extends GetStringKeys<Get<Obj, NestedKey>, NestedKeyOf<Get<Obj, NestedKey>>>
>(key: TargetKey): Get<Obj, `${NestedKey}.${TargetKey}`> {
return get(obj, [namespace, key].filter((part) => part != null).join('.'));
};
}
该函数与原始函数基本相同,只是现在它只需要生成字符串的键。处理可选的部分namespace
已搬入新的factory
功能。
我本可以选择更好的名字factory
and _factory
为了避免混淆,但希望您已经足够详细地跟随我完成了这个过程。
操场 https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbwOYFMYF84DMoRHAcgBsIATAQwGcALAgbgFgAoZmATzBTgDkVKYUpANIo2AeSwAeMQD44AXjhi4KAB4CAdqUpwIAIwBWKAMbwA-ImZxrcANpC4wDXADWoiFiUBdAFxwABgAkCA4AZHD8UE5I6P5wAD4BwWERMFEaMQB0wbz8giLiUmL2XjKxjEzotm5sHt5wfhooAG4oUBWsHFwA4mjSADRwAAoq6ihaOpHRcvJWw6Oa2kkITlhtcAXo2Ssaa1BwAEp8GP5z1hYFC+NLNXXKFr0w0vaiXoNH-HKNLW1nDfNqRY6W6ee5KWxDLz-JqtdrMTqcOCPMSGEwwAqUAYbK4TVLpJAzSxMawQxzOIS+JF9MSDIZyQHXSZpaJwCww9Z+IYVKoUjpMdiIx4AZWZGQxWIcDNxUwyhIQc1JTg2lMeWLpOKWMqQrPm31h3JKfOMEA0-F0hgUROsWAgED8BBtEAI-TmenIUD88uJNnI9oAEigiCRnX89J7jPaAOrQIikAjoOYJypwKhwY2mmB8rAAVw0pmAJrgAH0sORTNA2NILVKlrmXBoIAB3DSDXICYSiDU6Nv5UQSKsGGQyAAU+gMfhRBkGGnIID4YDLKD8PY7bAAlFa4FA0NmoM4c3mYAXnKgYAIoJI-gAVd2ny41nTC0VIcWqyet469tgyD95Vf9t9DF-dsCiHGQ5hHGo-BvKA71ENc-EAqdlhXTZthguC2FiOQvRsLcdz3OBT1HIC7BnOdKAXYwUEGGovEyLBgCIc9h2HBdYA3eQ5HY+AAEJFA0bMgzXTIDAgJxhwITICDXNcKmsdBuXhJgD3zQtS3LKBK1QzsHypGBJzRcUBRQOox2Ar9+xMszDDA4dyPnRczGXT9Vw3XD8JgXdnBLMsYArS9vWsBBiyLMAomacgBD8azPDHOB0BdIL9MM0xjK6GzkJ0wpJFi81B3pMZcWynUACIizCiKopQUr-n8CrwuASKBG2bLYjmAB6DqAAEYEoABaYAkAbbc4AAVV2aAvJnAQiDYOAAEk4FIE0CHgGBqCcFw4A2touGAHRyCI21SDgRtyHm-y4HCn4NHWzadDaXAoAg4cQoaqrovyhLp1nRzqJ1erKqa6rtgcyjF1if5yuB5qark5gk2YdMzVPc9LQ0-ytOHBGmDRtph1Kt0oEyPRMmMUq5LgLrCGjKBYwIZggA
顺便说一句,这对****来说是过度设计的;当你开始让类型像真正的代码一样工作时总是如此 ????