我有一个类似的代码:
// Those two come from backend, it cannot be guaranted
// they do not contain additional fields of any type.
interface Foo1 {
name: string;
}
interface Foo2 {
description: string;
}
// This one is mine and should always contain valid types
class Bar {
text: string;
constructor(data: Foo1 | Foo2 | Bar) {
this.text = data.name || data.description || data.text;
// Some more similar rules and throwing exceptions
// if types are incorrect
}
}
问题是TypeScript要求name
上不存在Bar
,而text
上不存在Foo
等,尽管实际上无论类型如何data
中的一个肯定会存在,使得this.text
始终设置为正确的类型。
如何在此处指定打字稿类型以避免此类问题,但仍在构造函数中同时接受两种类型?
ps。我不是在问instanceof
,它不适用于接口。我问的是正确地注释类型,仅此而已。改变代码的逻辑并不能解决我的问题。
答案 0 :(得分:1)
this.text =
'name' in data ? data.name
: 'description' in data ? data.description
: data.text;
问题是TS不允许检查表示没有这些属性的类型的值中的属性。每次您要访问此类内容时,都需要检查现有的此属性或通过as
还有更多信息,为什么-https://github.com/Microsoft/TypeScript/issues/12815
答案 1 :(得分:1)
缩小联合的方式始终是type guard。您永远不能访问联合的不常见属性。
使用类型防护,您可以将代码重写为:
class Bar {
text: string;
constructor(data: Foo1 | Foo2 | Bar) {
this.text = 'description' in data ? data.description:
'name' in data ? data.name :
data.text;
}
}
您可以创建一个自定义类型,以强制所有联合组成部分具有所有属性,但定义为undefined
。这将允许访问常用属性:
interface Foo1 {
name: string;
}
interface Foo2 {
description: string;
}
type KeyOfUnion<T> = T extends T ? keyof T : never;
type ExpandUnionHelper<T, K extends PropertyKey> = T extends T ? T & Partial<Record<Exclude<K, keyof T>, undefined>> : never
type ExpandUnion<T> = ExpandUnionHelper<T, KeyOfUnion<T>>
// This one is mine and should always contain valid types
class Bar {
text: string | undefined;
constructor(data: ExpandUnion<Foo1 | Foo2 | Bar>) {
this.text = data.text || data.name || data.description
}
}
但这不是确切的结果,因为它键入为string | undefined
最简单的解决方案可能是使用类型断言,虽然不是很优雅,但在这种情况下应该没问题。
// This one is mine and should always contain valid types
class Bar {
text: string;
constructor(data: Foo1 | Foo2 | Bar) {
this.text = (data as Foo1).name || (data as Foo2).description || (data as Bar).text;
// Some more similar rules and throwing exceptions
// if types are incorrect
}
}