繁体   English   中英

Typescript 递归 object 类型

[英]Typescript recursive object type

我想定义一个type / interface ,它能够包含与其自身相同类型的属性。

例如:

    type TMessagesFormat = { [key: string]: string };
    
    interface TMessages {
      messages: TMessagesFormat;
    }
    
    interface TGroupMessages {
      messages?: TMessagesFormat;
      controls: { [key: string]: TMessages | TGroupMessages }
    }
    
    let groupMessages: TGroupMessages = {
      controls: {
        username: { messages: {required: 'Username required'} }
      }
    }
    
    let messages: TGroupMessages = {
      controls: {
        username: { messages: { required: 'Username required' } },
        passwordGroup: {
          messages: { nomatch: 'Passwords doesn\'t match' },
          controls: {
            password: { messages: { required: 'Password required' } }
          }
        }
      }
    };

类型检查对 username 和 passwordGroup 工作正常,但例如 passwordGroup 中的控件可以是任何东西,TS 编译器不会抱怨。 事实上,如果我将controls: 'whatever'属性(字符串不应该是有效类型)放在username object 文字中,代码仍然可以在没有任何警告或错误的情况下编译。 这可能吗?如何实现? 谢谢!

看起来这是TypeScript的错误,在该错误中似乎并没有像您期望的那样对对象文字进行过多的属性检查。 最短的复制:

interface A {
    a: string;
}
interface B {
    b: number;
}
const a: A = {a: 'dog', b: 'cat'}; // error, b is unknown property
const ab: A | B = {a: 'dog', b: 'cat'}; // no error!

您可能会期望ab出现错误,例如'cat' is not a number ,但这不是因为上述问题。 如果该问题得到解决,那么您的问题就应该解决。


但是我们不必等待。 事实是,对对象文字进行多余的属性检查不是很有保护意义。 如果将未知属性添加到“新”对象文字中,它只会警告您。 在TypeScript中,向对象添加额外的属性没有什么错。 如果一个对象是有效的A ,即使您向其添加了额外的属性,该对象也仍然是一个有效的A 因此,如果您做一些事情来逃避多余的属性检查(例如,为其分配“非新鲜”文字),则可以添加所需的任何其他属性。

const dogCat = {a: 'dog', b: 'cat'};
const a: A  = dogCat; // no error

如果您真的要禁止额外的财产怎么办? 好吧,没有通用的方法可以对所有属性执行此操作。 但是,如果您要禁止特定的额外属性并知道其键名,则可以采用以下方法:

interface A {
    a: string;
    b?: never; // cannot have a defined b
}
interface B {
    b: number;
}
const dogCat = {a: 'dog', b: 'cat'};
const a: A  = dogCat; // error, string is not undefined
const ab: A | B = {a: 'dog', b: 'cat'}; // error, string is not number

现在,对于非新鲜aab您都会遇到错误。 所以,回到你的情况...


如果我们可以假设TMessages 不能具有controls属性:

interface TMessages {
  messages: TMessagesFormat;
  controls?: never; // no defined controls
}

interface TGroupMessages {
  messages?: TMessagesFormat;
  controls: { [key: string]: TMessages | TGroupMessages }
}

然后,如果一个TMessages | TGroupMessages TMessages | TGroupMessages具有已定义的controls属性,它必须是TGroupMessages指定的类型:

let tm: TMessages | TGroupMessages = {messages: {foo: 'hey'}, controls: 3}
// error, 'number' not assignable to type '{ [k: string]: TMessages | TGroupMessages }'

那应该为您工作。 希望能有所帮助; 祝好运!


TL; DR

等待Microsoft / TypeScript#22129得到解决,或者将您的TMessages接口更改为以下内容:

interface TMessages {
  messages: TMessagesFormat;
  controls?: never; // no defined controls
}

从 3.7 开始,这比以前更成为现实。 IMO TypeScript 一开始太严格了,但我想从设计语言的人的角度来看,最好让它太严格,然后放松语言,这样做是有意义的,或者它必须在的地方,这正是TS v3.7中发生的事情。 typescript 编译器调整得更宽松,允许引用自身出现的类型。


我可以给你各种各样的例子,但我认为最好的是证明你现在可以多么松散地递归定义你的类型

type TMessagesFormat = { [key: string]: TMessagesFormat };

interface TMessages extends TMessagesFormat{
   [key: string]: TMessagesFormat;
}

interface TGroupMessages {
   messages?: TMessagesFormat;
   controls: { [key: string]: TMessages | TGroupMessages };
}

以上是 100% 合法 TypeScript

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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