我有以下情况。我需要创建一个对象,该对象的属性可以是两种可能的类型(自定义类)之一。
export class MyType1 {
// some properties
}
export class MyType2 {
// some properties
}
class CustomType = MyType1 | MyType2
class Config {
propOne: boolean;
dynamicParam: { [key: string ]: CustomType }
}
Config 对象将按以下方式使用:
let config: Config = {
propertyOne: true,
dynamicParam: {
key1: {
// properties from one type
},
key2: {
// properties from another type
}
}
}
如果在定义对象时指定属性的类型,如下所示:
key1: <MyType1> {
}
我对MyType1类的属性有理解。但是,如果我不指定类型,则对MyType1和MyType2两个类的属性都具有智能感知(因为dynamicParam属性的类型为CustomType [union type])。
我的问题是,是否可以在定义对象时进行所需的类型定义,以便在尝试将属性定义为:
key1: {
}
我收到必须指定类型的错误消息?
答案 0 :(得分:3)
让我们修复您的代码,使其编译并具有一些特定的属性,以了解您的意思:
interface MyType1 {
a: string,
b: number,
c: boolean
}
interface MyType2 {
d: string,
e: number,
f: boolean
}
type CustomType = MyType1 | MyType2
我想也许您不满意下面的代码无怨无悔地编译:
let customType: CustomType = {
a: "yes",
b: 2,
c: false,
d: {what: "the"} // no error here
}
即使TypeScript中的类型不是"exact types"(其中值仅限于在类型定义中显式指定的属性),但是当您处理对象文字时,它们的行为更像是精确类型。当您将对象文字分配给变量时,文字会经受excess property checking的警告,如果文字包含任何意外的属性。例如,以下将是错误:
let warnCustomType: CustomType = {
a: "yes",
b: 2,
c: false,
nope: { what: "the" } // error
// Object literal may only specify known properties,
// and 'nope' does not exist in type 'CustomType'.
}
但是,对于CustomType
这样的联合类型(不是discriminated unions),多余的属性检查不适用于联合的每个组成部分;相反,它适用于整个工会。因此,不会警告您上面的d
属性,因为d
是CustomType
联合的至少一个组成部分的有效属性。这种现象被认为是bug,在以后的TypeScript版本中可能会得到解决。但是我不会屏住呼吸等待它。
您的建议以某种方式要求开发人员断言对象文字属性的类型很有趣,但我认为这是不可能的。您可以提出一个约定,但是该语言中的任何内容都不会强迫某人遵循该约定。
相反,您可能可以通过为所讨论的联合显式形成类似于确切类型的东西,然后要求该属性为该类型来摆脱困境。考虑使用conditional types的以下类型函数:
type AllPossibleKeysOf<U> = U extends any ? keyof U : never;
type Exclusify<U> = [U] extends [infer V] ? V extends any ? (
V & Partial<Record<Exclude<AllPossibleKeysOf<U>, keyof V>, never>>
) : never : never
我不会过多地介绍它们的工作原理,但基本上Exclusify<U>
将联合体U
分解成其组成部分,并形成一个新的联合体,其中每个组成部分都明确禁止使用联合中的其他类型。
让我们看看它在CustomType
上的作用:
type ExclusiveCustomType = Exclusify<CustomType>;
如果您检查它变为
(MyType1 & Partial<Record<"d" | "e" | "f", never>>) |
(MyType2 & Partial<Record<"a" | "b" | "c", never>>)
与
大致相同{a: string, b: number, c: boolean, d?: never, e?: never, f?: never} |
{a?: never, b?: never, c?: never, d: string, e: number, f: boolean}
类型为never
的那些可选属性实际上是不可用的,或者完全不存在,或者undefined
(如果存在)。现在,如果您尝试以下操作:
let customType: ExclusiveCustomType = {
a: "yes",
b: 2,
c: false,
d: {what: "the"} // error
}
在d
不是string | undefined
时会收到错误消息(因为如果使用联合的第一部分,则d
的类型为string
或{ {1}}(如果使用联合的第二部分)。如果尝试通过将其设置为undefined
来进行修复,则仍然会出现错误:
string
因此,let customType: ExclusiveCustomType = {
a: "yes",
b: 2,
c: false,
d: "nope"
} // error! Types of property 'd' are incompatible.
比ExclusiveCustomType
更具限制性。我不知道这是否是您想要的方式,但这是 a 方式。也许其他人会有不同的想法。希望能有所帮助。祝你好运!
答案 1 :(得分:0)
由于TypeScript具有结构类型系统,因此将接受具有MyType1
或MyType2
所有属性的对象文字。我不知道有任何合理的方法来要求像<MyType1>
这样的类型断言,但是如果您在类型定义中的某处添加了判别属性,则在配置中将需要它。例如,您可以这样做:
export enum MyTypes {
TYPE1, TYPE2
}
export class MyType1 {
kind: MyTypes.TYPE1;
// some properties
}
export class MyType2 {
kind: MyTypes.TYPE2;
// some properties
}
type CustomType = MyType1 | MyType2;
class Config {
propertyOne: boolean;
dynamicParam: { [key: string ]: CustomType }
}
let config: Config = {
propertyOne: true,
dynamicParam: {
key1: {
kind: MyTypes.TYPE2,
// properties from one type
},
key2: {
kind: MyTypes.TYPE1,
// properties from another type
}
}
};