Typescript递归对象类型

时间:2018-03-22 22:16:30

标签: typescript

所以我试图定义一个可以具有相同类型的嵌套属性的类型/接口。我的代码:

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属性(字符串不应该是有效类型),代码仍会编译而没有任何警告或错误。这可能吗?怎么样?谢谢!

1 个答案:

答案 0 :(得分:1)

看起来这是a bug in 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之类的错误,但由于上述问题,它不会出错。如果该问题得到解决,您的问题就会消失。

但我们不必等待。事实是对对象文字的多余属性检查不是非常保护。如果您向"fresh"对象文字添加未知属性,它会向您发出警告。在TypeScript中,向对象添加额外属性没有什么问题。如果对象是有效的A,则即使您向其添加额外属性,该对象仍然是有效的A。因此,如果你做了一些事情来逃避多余的属性检查(例如,为它分配一个“非新鲜”的文字),你就可以添加你想要的任何额外属性。

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

如果您真的想要禁止额外的财产怎么办?好吧,没有general way为所有属性做这件事。但是如果你想禁止特定的额外属性并知道他们的密钥名称,那么就有办法:

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具有已定义的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
}