我想找到一种方法,让嵌套对象路径的所有键都拥有所有 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
、{{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
答案 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
对中提取出更准确的结果,但我认为现在已经足够了。