使用带有Union类型签名的Index类型的意外行为

时间:2018-05-15 09:40:37

标签: typescript types

我有一个数据结构,其中一个键允许动态值集。我知道这些值的潜在类型,但我无法在Typescript中表达这一点。

interface KnownDynamicType {
    propA: boolean;
}

interface OtherKnownDynamicType {
    propB: number;
}

// I want to allow dynamic to accept either KnownDynamicType, OtherKnownDynamicType or a string as a value
interface DataModel {
    name: string;
    dynamic: {[key: string]: KnownDynamicType | OtherKnownDynamicType | string};
}

const data: DataModel = { // Set up some values
    name: 'My Data Model',
    dynamic: {
        someKnownType: {
            propA: true
        },
        someOtherKnownType: {
            propB: 1
        },
        someField1: 'foo',
        someField2: 'bar'
    }
}

data.dynamic.foo = 'bar'; // Fine
data.dynamic.someObject = { // <--- This should be invalid
    propA: false,
    propB: ''
}

Typescript似乎将data.dynamic.someObject视为KnownDynamicType,但出乎意料地允许我将OtherKnownDynamicType的属性分配给它。

我尽最大努力避免复杂的打字,但是当情况出现时,我宁愿避免放弃,只需设置dynamic: any

我的问题是,如何在打字稿中正确表达上述内容?

1 个答案:

答案 0 :(得分:3)

联盟确实是包容性的,因此A | B包含任何有效的A,以及任何有效B的内容,包括任何有效的A & B (A | B) & !(A & B) 1}}。 TypeScript没有negated types,因此您无法说出像A这样的内容,它会明确排除任何与BA匹配的内容。哦,好吧。

TypeScript也缺少exact types,因此向有效的A添加属性仍会产生有效的Exact<A> | Exact<B>。因此,您无法说出A,这也可以排除与BA匹配的任何内容,以及除明确之外的任何其他属性的内容声明了Btype ProhibitKeys<K extends keyof any> = { [P in K]?: never } type Xor<T, U> = (T & ProhibitKeys<Exclude<keyof U, keyof T>>) | (U & ProhibitKeys<Exclude<keyof T, keyof U>>); 的属性。哦,好吧。

TypeScript 确实拥有excess property checking,它通过禁止&#34; fresh&#34;提供额外属性来提供否定或确切类型的一些好处。对象文字。不幸的是,(从TypeScript v2.8开始)有一个issue,其中对联合类型的多余属性检查并不像大多数人想要的那样严格,正如您所发现的那样。看起来这个问题被认为是一个错误,应该在 TypeScript v3.0 (编辑:)中解决未来,但这并不能解决你今天的问题。哦,好吧。

所以,也许我们可以使用我们的类型来获得类似于你想要的行为:

ProhibitKeys<K>

让我们检查一下。 K返回一个类型,其中包含所有可选属性,这些属性的键位于never且其值为ProhibitKeys<"foo" | "bar">。因此{foo?: never, bar?: never}相当于never。由于ProhibitKeys<K>impossible type,因此真实对象与K匹配的唯一方法是不要使用其键位于Xor<T,U>的任何属性。因此这个名字。

现在让我们看一下A。类型Exclude<keyof A, keyof B>B中所有已声明的键的并集,这些键在Xor<T,U>中也不会显示为键。 T & ProhibitKeys<Exclude<keyof U, keyof T>>是两种类型的联合。第一个是T,这意味着它是有效的U,没有来自T的属性(除非这些属性也在U & ProhibitKeys<Exclude<keyof T, keyof U>>中)。第二个是U,这意味着有效的T没有来自DataModel的属性。这与我在TypeScript中表达你想要的独占联盟的想法非常接近。让我们使用它,看看它是否有效。

首先,更改interface DataModel { name: string; dynamic: { [key: string]: Xor<KnownDynamicType, OtherKnownDynamicType> | string }; }

declare const data: DataModel;
data.dynamic.foo = 'bar'; // okay

data.dynamic.someObject = { 
  propA: false,
  propB: 0
}; // error!  propB is incompatible; number is not undefined.

data.dynamic.someObject = {
  propA: false
}; // okay now

data.dynamic.someObject = {
  propB: 0
}; // okay now

data.dynamic.someObject = { 
  propA: false,
  propC: 0  // error! unknown property
}

让我们看看会发生什么:

1- <a  class="godown" id="<?=$row->id_apoint;?>"><?=$row->nombre;?></a>
2- <button  class="bill" >button1</button>  <button  class="bill" >button2</button>

这一切都按预期工作。希望有所帮助。祝你好运!