我尝试将以下函数中的字符串类型替换为更具体的类型,以确保类型安全的属性访问:
import {get} from 'lodash';
const obj = {
foo: 'foo',
bar: {
a: 'Hello',
b: {c: 'World'}
}
};
function factory(namespace?: string) {
return function getter(key: string) {
return get(obj, [namespace, key].filter((part) => part != null).join('.'));
};
}
const getter = factory('bar');
getter('b.c'); // 'World'
Run Code Online (Sandbox Code Playgroud)
点符号表示嵌套的属性访问。它既可以出现在 中namespace,也可以出现在 中key。
到目前为止,我发现我可以namespace使用这个实用程序输入:
type NestedKeyOf<ObjectType extends object> =
{
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
: `${Key}`
}[keyof ObjectType & (string | number)];
Run Code Online (Sandbox Code Playgroud)
用法:namespace?: NestedKeyOf<typeof obj>。
然而,我正在努力想出一个自动分配给key.
另外两个要求是:
可以假设对象中只存在对象和字符串,没有其他内容。
// Test cases
// Should pass
factory()('foo')
factory('bar')('a')
factory('bar')('b.c')
// Invalid property access
// @ts-expect-error
factory('baz')
// @ts-expect-error
factory('bar')('d')
// Only partial namespaces are allowed
// @ts-expect-error
factory('foo')
// Getter calls need to resolve to a leaf string
// @ts-expect-error
factory('bar')('b')
Run Code Online (Sandbox Code Playgroud)
任何帮助将非常感激!非常感谢!
我不敢相信这个可恶的东西真的有效:
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('.'));
};
}
Run Code Online (Sandbox Code Playgroud)
请允许我解释一下。首先,我在执行 NestedKeyOf 时遇到了很多麻烦:(
我必须重写它,因为它报告类型实例化太深,所以这是我的版本:
type NestedKeyOf<O> = O extends object ? {
[K in keyof O]: `${K & string}` | `${K & string}.${NestedKeyOf<O[K]>}`;
}[keyof O] : never;
Run Code Online (Sandbox Code Playgroud)
它做同样的事情;只是写法不同。接下来我们需要能够“深度获取”一个属性(模仿 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;
Run Code Online (Sandbox Code Playgroud)
额外的功能extends keyof O是为了防止无效属性访问时出现错误。
最后就是我上面展示的怪物。
我们需要存储实际namespace内容,因此我们使用泛型。通过这个恰当命名的泛型,NestedKey我们现在可以在 的定义中使用它getter。
getter还需要一个嵌套键。namespace但是,如果未提供,其类型会有所不同。
这就是为什么NestedKey extends undefined存在。如果不存在,则key应该是原始对象的嵌套键。namespace否则,它是指向的值的嵌套键,使用Get.
最后在返回值中,我们做了同样的事情。如果NestedKey不存在,则我们深度获取目标键,否则,我们深度获取嵌套键和目标键。
断言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];
Run Code Online (Sandbox Code Playgroud)
它们采用对象类型和一些潜在的键。它检查每个键的类型。对于对象,如果它是字符串,则为never,否则为P。我们之所以使用 ,never是因为下面我们将得到所有剩余键的并集[K]并T | never简化为T。
然后由于namespace是可选的它引入了一些困难。我的一个错误是相信如果namespace没有提供,NestedKey将是未定义的(所以前面的答案实际上是不正确的)。稍后将对此进行更正。
为了解决可选的问题namespace,我们将原始factory函数的参数设置namespace 为非可选,并将其重命名为_factory(internal/private)。然后我们创建一个新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");
}
Run Code Online (Sandbox Code Playgroud)
它需要任何对象键,但如果未提供命名空间,它将创建默认值__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('.'));
};
}
Run Code Online (Sandbox Code Playgroud)
该函数与原始函数基本相同,只是现在它只需要生成字符串的键。处理可选的部分namespace被移至新factory函数中。
我本可以选择更好的名称来factory避免_factory混淆,但希望您能很好地跟随我完成这一切。
顺便说一句,这对****来说是过度设计的;当你开始让类型像真正的代码一样工作时总是如此
| 归档时间: |
|
| 查看次数: |
759 次 |
| 最近记录: |