简体   繁体   English

Typescript 递归 object 类型

[英]Typescript recursive object type

I want to define a type / interface that is able to contain properties of the same type as itself.我想定义一个type / interface ,它能够包含与其自身相同类型的属性。

For Example:例如:

    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' } }
          }
        }
      }
    };

Type checking works fine for username and passwordGroup, but eg controls in passwordGroup can be anything and TS compiler doesn't complain.类型检查对 username 和 passwordGroup 工作正常,但例如 passwordGroup 中的控件可以是任何东西,TS 编译器不会抱怨。 In fact if I put controls: 'whatever' property (string shouldn't be a valid type) inside username object literal, code still compiles without any warning or error.事实上,如果我将controls: 'whatever'属性(字符串不应该是有效类型)放在username object 文字中,代码仍然可以在没有任何警告或错误的情况下编译。 Is this possible and how?这可能吗?如何实现? Thanks!谢谢!

It looks like this is a bug in TypeScript where excess property checks on object literals don't seem to happen for unions the way you'd expect. 看起来这是TypeScript的错误,在该错误中似乎并没有像您期望的那样对对象文字进行过多的属性检查。 The shortest reproduction: 最短的复制:

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!

You would expect ab to error with something like 'cat' is not a number , but it doesn't because of the above issue. 您可能会期望ab出现错误,例如'cat' is not a number ,但这不是因为上述问题。 If and when that issue gets addressed, your problem should go away. 如果该问题得到解决,那么您的问题就应该解决。


But we don't have to wait. 但是我们不必等待。 The fact is that excess property checking on object literals isn't very protective. 事实是,对对象文字进行多余的属性检查不是很有保护意义。 It just warns you if you add an unknown property to a "fresh" object literal. 如果将未知属性添加到“新”对象文字中,它只会警告您。 In TypeScript there's nothing really wrong with adding extra properties to an object. 在TypeScript中,向对象添加额外的属性没有什么错。 If an object is a valid A , that same object is still a valid A even if you add extra properties to it. 如果一个对象是有效的A ,即使您向其添加了额外的属性,该对象也仍然是一个有效的A So if you do something to evade the excess property checking (eg, assign a "non-fresh" literal to it), you're allowed to add any extra properties you want. 因此,如果您做一些事情来逃避多余的属性检查(例如,为其分配“非新鲜”文字),则可以添加所需的任何其他属性。

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

What if you really want to disallow extra properties? 如果您真的要禁止额外的财产怎么办? Well, there's no general way to do it for all properties. 好吧,没有通用的方法可以对所有属性执行此操作。 But if you want to disallow particular extra properties and know their key names, there's a way to do it: 但是,如果您要禁止特定的额外属性并知道其键名,则可以采用以下方法:

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

Now you get errors for both non-fresh a and ab . 现在,对于非新鲜aab您都会遇到错误。 So, back to your case... 所以,回到你的情况...


If we can assume that TMessages cannot have a controls property: 如果我们可以假设TMessages 不能具有controls属性:

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

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

Then, if a TMessages | TGroupMessages 然后,如果一个TMessages | TGroupMessages TMessages | TGroupMessages has a defined controls property, it must be of the type specified in TGroupMessages : TMessages | TGroupMessages具有已定义的controls属性,它必须是TGroupMessages指定的类型:

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

And that should work for you. 那应该为您工作。 Hope that helps; 希望能有所帮助; good luck! 祝好运!


TL;DR TL; DR

Wait for Microsoft/TypeScript#22129 to be addressed, or change your TMessages interface to the following: 等待Microsoft / TypeScript#22129得到解决,或者将您的TMessages接口更改为以下内容:

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

As of 3.7 this become more of a reality than it was before.从 3.7 开始,这比以前更成为现实。 IMO TypeScript was to strict at first, but I would imagine from the perspective of the person(s) engineering the langauge, that it would be better to make it too strict, and then loosen up the language, where it made sense to do so, or where it had to be, which is exactly what happened in TS v3.7 . IMO TypeScript 一开始太严格了,但我想从设计语言的人的角度来看,最好让它太严格,然后放松语言,这样做是有意义的,或者它必须在的地方,这正是TS v3.7中发生的事情。 The typescript compiler was adjusted to be more loose , allowing references to types that occur in themselves. typescript 编译器调整得更宽松,允许引用自身出现的类型。


I could give you all sorts of examples, but I think the best, would be to demonstrate how loosely you could recursively define your types now我可以给你各种各样的例子,但我认为最好的是证明你现在可以多么松散地递归定义你的类型

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

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

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

The Above is 100% Legal TypeScript以上是 100% 合法 TypeScript

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

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