【问题标题】:Typescript: set value type of a deep nested object pathTypescript:设置深度嵌套对象路径的值类型
【发布时间】:2021-08-08 22:07:40
【问题描述】:

我想找到一种方法来为嵌套对象路径的所有键提供所有 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.boat1 其 value type is string、boats.boat2 其 value type is number、ships 其 value type is Array&lt;Ship&gt;。

但无法为嵌套路径ships.0.shipName设置value type。

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

以下是我在打字稿游乐场中为深层嵌套对象路径设置值类型的尝试:

Playground link for seeting value type for deep nested object paths

【问题讨论】:

  • 这能回答你的问题吗? stackoverflow.com/questions/67606750/…
  • 对不起,它没有回答,但感谢您调查。
  • 好吧,我相信问题是0 索引未强制以Array&lt;Ship&gt; 类型呈现。如果您将ships 的类型切换为[Ship],您的代码就可以工作。
  • 请注意,setValue() 函数中的 V 类型参数实际上并没有做任何事情;您可能应该删除它并将所有对V 的引用直接替换为PathValue&lt;T, P&gt;。

标签: typescript


【解决方案1】:

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


一个极端情况是number-to-stringliteral type 转换使用模板文字很容易完成,但很难将string 文字转换为等效的number 文字(请参阅this question 和回答以获取更多信息)。

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

type Oops = (string[])["0"] // error!
// ------------------> ~~~
// Property '0' does not exist on type 'string[]'

type Okay = (string[])[0] // okay
// type Okay = string

因为"0" 不被视为数组的键,所以当您的PathValue 类型函数评估"0" extends keyof Ship[] 时,"ships.0.shipName" 会失败,您会很伤心。如果没有将"0" 转换为0 或让编译器将"0" 视为keyof Ship[] 的官方方法,就没有规范的解决方案。


所以你有点被各种变通方法困住了。一种可能是忽略元组的可能性(除了那些讨厌的rest elements in tuple types 之外,它们大多已经有明确的数字字符串索引),只需对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&lt;T, P&gt; 类型中使用它,像这样:

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 和K 对中找出更准确的结果,但我认为目前这已经足够了。

Playground link to code

【讨论】:

  • 感谢您的精彩回答。您对 typescript 编译器工作原理的了解对 typescript 社区有很大帮助。
猜你喜欢
  • 2020-02-15
  • 2022-01-12
  • 2021-08-31
  • 2021-07-14
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多