如何对具有1个已知属性和1个属性的模型进行建模,而我知道类型,但是不知道属性名称。
我认为它应该是递归类型,但是我不确定如何在打字稿中做到这一点。
所以我有这种安排:
export const hierarchy: Hierarchy = {
location: {
name: "location",
regions: [
{
name: "West Coast",
states: [
{
name: "California",
cities: [
{ name: "San Diego", sites: [{name: "site 1"},
{ name: "Los Angeles" },
{ name: "San Jose" },
],
},
],
}
],
},
};
因此,每个对象都有一个名称字段和一个相同类型且深层次的数组。
这是我幼稚的尝试,显然不起作用:
export interface Graph<T> {
name: string;
[key: string]: Graph[]
}
答案 0 :(得分:0)
TypeScript目前尚无法将directly express或除string
以外的所有'name'
作为一种类型,更不用说key type of an index signature或映射类型。因此,您想要说类似:
// this is not valid TypeScript (yet?)
interface Graph {
name: string,
[k: string & not "name"]: Array<Graph>
}
但是你不能。也许这最终会发生,但是现在这只是一个梦想。
您可以 使用mapped和conditional类型将Graph
表示为对对象类型的约束。以及generics。这个想法是,您拥有通用类型别名Graph
而不是VerifyGraph<T>
,该别名将您希望有效的T
的候选类型作为参数Graph
。如果T
是有效的Graph
,则VerifyGraph<T>
将与T
相同。否则,将通过修复任何不正确的属性将VerifyGraph<T>
更改为有效的Graph
。最后,创建一个通用辅助函数asGraph<T>()
,该函数采用类型T & VerifyGraph<T>
的参数。编译器将推断类型T
作为参数的类型,然后它将检查VerifyGraph<T>
以确保其也符合。如果可以,那么很好,因为T & VerifyGraph<T>
只会是T & T
或T
。否则,您会收到一条错误消息,因为T & VerifyGraph<T>
不是T
。
此外,由于您似乎正在创建一个包含Hierarchy
的{{1}},因此我们可以将其表示为具有自己的辅助函数的泛型类型:
Graph
它在这里工作:
type VerifyGraph<T> = T extends { name: string }
? { [K in keyof T]:
K extends "name" ? string :
T[K] extends Array<infer E> ? Array<VerifyGraph<E>> :
Array<{ name: string }>
} : { name: string }
const asGraph = <T>(x: T & VerifyGraph<T>): T => x;
interface Hierarchy<T> {
location: T & VerifyGraph<T>
}
const asHierarchy = <T>(x: Hierarchy<T>): Hierarchy<T> => x;
这是一些错误:
// okay
const hierarchy = asHierarchy({
location: {
name: "location",
regions: [
{
name: "West Coast",
states: [
{
name: "California",
cities: [
{
name: "San Diego",
sites: [
{ name: "site 1" },
{ name: "Los Angeles" },
{ name: "San Jose" },
],
},
],
}
],
},
]
}
});
我想所有人都应有表现。
这可能比您想在代码中使用的更为复杂。这里最大的缺点是const badHierarchy1 = asHierarchy({
location: {
name: "Okay",
notAnArray: 123 // error!
// ~~~~~~~~~~ <-- Type 'number' is not assignable to type '{ name: string; }[]'.
}
})
const badHierarchy2 = asHierarchy({
location: { // error!
// ~~~~~~~~ <-- Property 'name' is missing in type '{ noName: never[]; }'
noName: []
}
})
const badHierarchy3 = asHierarchy({
location: {
name: "level1",
stuff: [
{
name: "level2",
deeperNotAnArray: "oopsie"; // error!
// ~~~~~~~~~~~~~~~~ <--
// Type 'string' is not assignable to type '{ name: string; }[]'.
}
]
}
})
不再是具体类型,而是通用类型,因此处理它的每个函数和类型都需要添加通用参数。因此,您可能只想将此限制放在用户调用的函数中以传递某些内容,然后在处理该函数的库中使用诸如
Graph
相反。每个有效的interface LooseGraph {
name: string,
[k: string]: string | Array<LooseGraph>
}
都可以分配给T & Graph<T>
,反之亦然,因此您可能必须在库中做一些断言。但这是我认为您目前可以使用TypeScript做到的最好的选择。
好的,希望能有所帮助;祝你好运!