依赖于Typescript的嵌套对象属性

u0njafvf  于 2023-03-24  发布在  TypeScript
关注(0)|答案(1)|浏览(99)

有没有可能使几个嵌套的对象属性相互依赖
大概是这样的

type func = (props: any) => ...
type Obj = {
   [key: string]: {
      f: func, args: PARAMETERS OF "f", 
      f1: func, args1: PARAMETERS OF "f1" 
      ...
   }
}
const demo: Obj = {
   // here the type of "args" should be {name: string, age: number}
   key1: {f: (props: {name: string, age: number}) => ..., args: HERE SHOULD BE TYPED},  

   // here the type of "args" should be {somethingRandom: number[]}
   key2: {f: (props: {somethingRandom: number[]}) => ..., args: HERE SHOULD BE TYPED},

   ... more properties
}
bq3bfh9z

bq3bfh9z1#

TypeScript中没有与您希望Obj接受的值完全对应的特定类型。相反,您可以编写一个generic类型Obj<T>,它充当T的约束,以便如果T extends Obj<T>,则T是可接受的。否则Obj<T>将是T的某个“正确”版本。然后您可以编写一个通用的助手函数asObj(obj),它返回其输入,但是如果obj对于某些T不是有效的Obj<T>,则会警告您。
也就是说,我们要写

type Obj<T> = ⋯;

然后就像

const asObj = <T extends Obj<T>>(obj: T) => obj;

但更有可能的是

const asObj = <T,>(obj: T & Obj<T>): T => obj;

因为前一个版本可能不起作用(可能是循环约束),并且可能产生不合适的输出类型(可能是Obj<{⋯}>而不是{⋯},这可能是不同的)。
现在的问题是如何定义Obj<T>
由于Obj类型的每个属性都是独立测试的,因此我们可以将其设置为mapped type,而不是其他检查单个属性的类型函数:

type Obj<T> = { [K in keyof T]: F<T[K]> }

现在我们需要定义F<>

type F<T> =
   { [S in Suffixes as `f${S}`]: 
       (props: never) => void 
   } & { [S in Suffixes as `args${S}`]: 
       T extends Record<`f${S}`, (props: infer P) => void> ? P : never 
   }

type Suffixes = "" | "1" | "2" // <-- set this to however many you need

所以F<T>应该验证T是一个有效的Obj属性。(您可以通过修改Suffixes联合来选择数量)。我们需要ff1f2等,是一个参数的函数(函数(props: never) => void将接受任何one-arg函数),我们需要argsargs1args2等,是相应f函数的有效参数类型。这些都是用Record实用程序类型和条件类型推断表示的;本质上,对于每个argXX,它在T中查找fXX函数并推断其参数类型。
好了,让我们来测试一下:

const asObj = <T,>(obj: T & Obj<T>): T => obj;

const demo = asObj({
   key1: {
      f: (props: { name: string, age: number }) => { }, args: { age: 3, name: "" },
      f1: (props: { name: 2 }) => { }, args1: { name: 2 },
      f2: (props: unknown) => { }, args2: 2
   },
   key2: {
      f: (props: { x: boolean }) => { }, args: { x: true },
      f1: (props: { z: Date }) => { }, args1: { z: new Date() },
      f2: (props: number) => { }, args2: "oops" // error
   },
});

demo.key1.f(demo.key1.args); // okay
demo.key2.f1(demo.key2.args1); // okay

看起来不错。编译器强制每个arg对应于每个f,如果你犯了错误,你会得到一个错误。而且demo的类型足够强大,它可以记住哪些参数对应于哪些方法。
Playground代码链接

相关问题