假设我有一个字典,像下面这样:
const obj = {
something: 123,
otherThing: "asd",
nested: {
nestedSomething: 456,
nestedOther: "fgh",
deepNested: {
deepNested1: "hello",
deepNested2: 42,
deepNestedArr: ["a", "b", "c"],
},
},
};
我希望有一个函数access
,它可以用来访问这个字典的值,如下所示:
access(obj, "something") //should return number
access(obj, "nested", "nestedOther") //should return string
access(obj, "nested", "deepNested", "deepNestedArr") //should return string[]
//and even:
access(obj, "nested", "deepNested", "deepNestedArr", 0) //should return string
为此,我首先需要一个实用程序类型,它可以获取一个对象类型,并输出该对象中所有可能的叶子路径的并集.我是这样实现的:
type AllPaths<Obj extends object, Key = keyof Obj> = Key extends keyof Obj
? Readonly<Obj[Key]> extends Readonly<Array<any>>
? [Key] | [Key, number]
: Obj[Key] extends object
? [Key] | [Key, ...AllPaths<Obj[Key]>]
: [Key]
: never;
当给定一个具体类型作为参数时,它就可以工作:
type Test = AllPaths<typeof obj>;
//["something"] | ["otherThing"] | ["nested"] | ["nested", "nestedSomething"] | ["nested", "nestedOther"] | ["nested", "deepNested"] | ["nested", "deepNested", "deepNested1"] | [...] | [...] | [...]
然后,我需要一个实用程序类型,它接受对象类型和我们前面生成的路径,索引到对象中,并返回结果类型.我是这样实现的:
type GetTypeFromPath<Obj extends object, Path extends PropertyKey[]> = Path extends [
infer Head,
...infer Tail extends PropertyKey[]
]
? Head extends keyof Obj
? Obj[Head] extends object
? GetTypeFromPath<Obj[Head], Tail>
: Obj[Head]
: never
: Obj;
...当给出具体的论证时,它也起作用.
type Test2 = GetTypeFromPath<typeof obj, ["nested", "deepNested", "deepNested2"]> //number
当给定具体类型时,这些实用程序独立工作,并且它们是高性能的.
现在,如果我试图在一个带有泛型类型参数的函数中使用它们,tsserver会挂起一点,然后给出"类型实例化过度深... "或"堆栈深度比较类型... "错误取决于我如何设置泛型.无论我做什么,我都无法避免触发限制器.有没有合理的方法来实现这一点?
declare function access<
Obj extends object,
//providing the default only does not work without extends for some reason
Paths extends AllPaths<Obj> = AllPaths<Obj>,
Ret = GetTypeFromPath<Obj, Paths>
>(
obj: Obj,
...path: Paths
): Ret;
const res = access(obj, "nested", "deepNested");
在上面,泛型类型Ret无法计算,因为它太深了.
这一切都是为了API边界,所以如果输入了错误的对象键路径,那么在access
调用点有错误,并且在输入函数键时拥有适当的智能感知就足够了.