[英]Typescript recursive type with indexer
我想定义一个这样的递归类型,基本上是这样的:
interface CSSProperties {
marginLeft?: string | number
[key: string]?: CSSProperties
}
不幸的是, 打字稿文档说:
尽管字符串索引签名是描述“字典”模式的强大方法,但它们也强制所有属性与其返回类型匹配。 这是因为字符串索引声明obj.property也可以作为obj [“ property”]使用。 在以下示例中,名称的类型与字符串索引的类型不匹配,并且类型检查器给出错误:
这似乎无法用打字稿来表达,这似乎是一个严重的局限。 Flow在这里做了我认为正确的事情,并假设marginLeft
不属于索引规范。
在TypeScript中根本可以做到吗? 或者,是否可以指定一个字符串是除一组字符串之外的任何字符串? 这样,我可以做大概的事情:
interface NestedCSSProperties: CSSProperties {
[P not in keyof CSSProperties]?: CSSProperties
}
这里的问题不是真正的递归(允许),而是您指出的签名冲突。
我不确定当前行为的反面是否正确。 对于我来说,这似乎是一种主观决定,可以同时为两种方式做出案例。 照原样接受示例意味着您将隐式合并定义。 您可以查看其中的一行,并假定界面的效果,而另一行更改结果。 编写起来确实感觉更自然,但是我不确定它是否像您通常不会期望的那样安全。
而不管。 TypeScript确实具有与您期望的行为类似的行为,但是必须明确指出,字符串键也可以是string
或number
类型。 这将起作用:
interface CSSProperties {
marginLeft?: string | number,
[key: string]: CSSProperties|string|number,
}
例如,对于上面的接口,这是有效的:
let a: CSSProperties = {
marginLeft: 10,
name: {
marginLeft: 20,
}
};
这不是:
let a: CSSProperties = {
marginLeft: 10,
something: false, // Type 'boolean' is not assignable to type 'string | number | CSSProperties'.
something: new RegExp(/a/g), // Type 'RegExp' is not assignable to type 'CSSProperties'.
name: {
marginLeft: 20,
},
car: ["blue"], // Type 'string[]' is not assignable to type 'CSSProperties'.
};
它会正确地知道命名成员:
let name1: string | number = a.marginLeft; // OK, return type is string | number
a.marginLeft = false; // Blocked, Type 'false' is not assignable to type 'string | number'.
a["whatever"] = false; // Blocked, Type 'false' is not assignable to type 'string | number | CSSProperties'.
a["marginLeft"] = false; // Blocked, Type 'false' is not assignable to type 'string | number'.
但是,这里的问题是,您需要在读取时CSSProperties
其他动态成员-它不知道这是CSSProperties
。
这不会被阻止:
a["whatever"] = 100;
它会抱怨:
let name3: CSSProperties = a["name"]; // Type is CSSProperties | string | number
但是,如果您明确键入,这将起作用:
let name3: CSSProperties = a["name"] as CSSProperties;
在这种情况下,您必须包含字符串和数字。 然后,您可以定义替代。 使用此示例,如果您尝试将地图分配给'marginLeft',则TS会投诉,但会允许其他所有事情。
任何其他限制,您可能需要编写更深入的.d.ts文件,该文件可能使用映射的类型。
您可以使用诸如Pick的预设映射类型来简化专有属性,但我承认,有时候我很难将它们包裹在它们周围。
interface IMap<T> {
[key: string]: T
}
type CSSPropertyValue = string | number | CSSPropertiesBase;
interface CSSPropertiesBase extends IMap<CSSPropertyValue>
{
}
interface CSSProperties extends CSSPropertiesBase
{
marginLeft?: string|number;
}
您是否尝试过使用像这样的路口类型?
interface CssPropertyValues {
marginLeft? :string |number;
}
interface RecursiveCssProperties {
[key: string]: CssProperties;
}
type CssProperties = CssPropertyValues & RecursiveCssProperties;
let foo: CssProperties = {};
let myMargin = foo.bar.really.foobar.marginLeft; //should work... myMargin is typed as string|number|undefined
交集类型是接口继承的一种经常被忽略且功能更强大的替代方法
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.