繁体   English   中英

带索引器的Typescript递归类型

[英]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确实具有与您期望的行为类似的行为,但是必须明确指出,字符串键也可以是stringnumber类型。 这将起作用:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM