打字稿:设置深度嵌套对象路径的值类型

时间:2021-05-19 16:19:14

标签: typescript

我想找到一种方法,让嵌套对象路径的所有键都拥有所有 value types

我在一定程度上成功了,但未能为数组对象内的深层嵌套属性设置值类型。

interface BoatDetails {
  boats: {
    boat1: string;
    boat2: number;
  };
  ships: Array<Ship>
}
interface Ship {
    shipName: string
}
const boatDetails: BoatDetails = {
  boats: {
    boat1: "lady blue",
    boat2: 1,
  },
  ships: [
      {shipName: "lacrose"}
  ]
};

对于上面的代码,我能够成功设置嵌套对象路径的值类型,例如 boats.boat1value type is stringboats.boat2value type is number、{{1} } 其ships

但无法为嵌套路径 value type is Array<Ship> 设置 value type

我从以下链接中参考了设置深层嵌套对象路径类型: Typescript: deep keyof of a nested object

下面是我在 typescript 操场中为深层嵌套对象路径设置值类型的尝试:

Playground link for seeting value type for deep nested object paths

1 个答案:

答案 0 :(得分:2)

请注意:在 Paths<T>PathValue<T, P> 中进行的 recursive conditional types 操作和 template literal type 操作会对编译器造成负担(您可以轻松获得明确的递归限制警告,或者更糟的是,编译时间呈指数级增长)并且有各种边缘情况。


一个边缘情况是,number-to-string literal type 转换如此轻松地使用模板文字完成,但不容易将 string 文字转换为等效的 {{ 1}} 个文字(有关详细信息,请参阅 this question 和答案)。

所以你得到了一个像 number 这样的索引类型,你想用它作为数组类型的键,但除非该数组类型恰好是一个 tuple,否则编译器不会让你这样做:

"0"

并且因为 type Oops = (string[])["0"] // error! // ------------------> ~~~ // Property '0' does not exist on type 'string[]' type Okay = (string[])[0] // okay // type Okay = string 不被视为数组的键,所以 "0" 在您的 "ships.0.shipName" 类型函数计算 PathValue 时失败,您很伤心。如果没有将 "0" extends keyof Ship[] 转换为 "0" 或让编译器将 0 视为 "0" 的官方方法,没有规范的解决方案。


所以你有点被各种变通方法困住了。一种可能是忽略元组的可能性(除了那些讨厌的 rest elements in tuple types 之外,它们大多已经有了显式的数字字符串索引),而只是对 keyof Ship[] 做一个解决方法来检查 T[K] 是否有T 索引签名,并且 number 可分配给 K,如果是,则返回 `${number}`

T[number]

现在有效:

type Idx<T, K> = K extends keyof T ? T[K] :
    number extends keyof T ? K extends `${number}` ? T[number] : never : never;

如果我们在您的 type TryThis = Idx<string[], "0"> // type TryThis = string type StillWorks = Idx<string[], 0> // type StillWorks = string 类型中使用它,如下所示:

PathValue<T, P>

然后开始工作:

type PathValue<T, P extends Paths<T, 4>> = P extends `${infer Key}.${infer Rest}`
  ? Rest extends Paths<Idx<T, Key>, 4>
  ? PathValue<Idx<T, Key>, Rest>
  : never
  : Idx<T, P>

还有其他可能的解决方法可以从更任意的 setValue( boatDetails, `ships.0.shipName`, "titanic" ); // okay /* function setValue<BoatDetails, "ships.0.shipName">( obj: BoatDetails, path: "ships.0.shipName", value: string ): BoatDetails */ T 对中提取出更准确的结果,但我认为现在已经足够了。

Playground link to code