TypeScript:类型'Foo |中不存在属性'foo'酒吧'

时间:2019-12-12 14:37:53

标签: typescript

我有一个类似的代码:

// 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,它不适用于接口。我问的是正确地注释类型,仅此而已。改变代码的逻辑并不能解决我的问题。

2 个答案:

答案 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;
    }
}

Playground Link

您可以创建一个自定义类型,以强制所有联合组成部分具有所有属性,但定义为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
    }
}

Playground Link

但这不是确切的结果,因为它键入为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
    }
}

Playground Link