Typescript数组:使用混合类型推断

时间:2018-10-09 13:21:51

标签: typescript

打字稿版本3.0.3

我正在为侧边栏导航菜单创建模型,item可以是以下所述的两种类型之一:

type SidebarItems = Array<SimpleSidebarItem | ComplexSidebarItem>;

abstract class SidebarItem {
    title: string;
}

class SimpleSidebarItem extends SidebarItem {
    url : string;
}

class ComplexSidebarItem extends SidebarItem {
    subItems: Array<{
        title: string;
        url : string;
    }>
}
  • 如果它是SimpleSidebarItem,则必须具有url,但没有子项。
  • 这是一个ComplexSidebarItem,它不应具有url,但必须具有subItems。

我无法正常工作- 这应该不是有效的输入,但显示正常:

const items: SidebarItems = [{title: '', url: '', subItems: [{title: '', url: ''}]}];

推理无法按预期进行:

const items: SidebarItems = [{title: '', url: ''}, {title: '', subItems: [{title: '', url: ''}]}];
const shouldBeComplexSidebarItem = items[1];
ShouldBeComplexSidebarItem的

类型是SimpleSidebarItem | ComplexSidebarItem。

我在这里想念什么?

1 个答案:

答案 0 :(得分:2)

这里有两个问题。

第一个涉及与工会有关的多余财产检查。您可以阅读关于类似问题的答案here。其要点是,多余的联合检查属性允许对象上存在任何成员的任何键。我们可以通过引入额外的类型的成员来避免这种情况,即永远不要确保具有过多属性的对象与特定成员不会错误兼容:

type SidebarItems = Array<StrictUnion<SimpleSidebarItem | ComplexSidebarItem>>;

type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>


const items2: SidebarItems = [{title: '', url: '', subItems: [{title: '', url: ''}]}]; //error
const items3: SidebarItems = [{title: '', subItems: [{title: '', url: ''}]}]; //ok

第二个问题与以下事实有关:如果您指定变量的类型,打字稿将不会做任何额外的推断,因此items[1]ComplexSidebarItem的信息将会丢失,所有打字稿都会知道一个项目可以是SimpleSidebarItem | ComplexSidebarItem

我们可以使用类型保护来检查类型:

const items: SidebarItems = [{title: '', url: ''}, {title: '', subItems: [{title: '', url: ''}]}];
const shouldBeComplexSidebarItem = items[1];
if(!('url' in shouldBeComplexSidebarItem)){ //type guard
    shouldBeComplexSidebarItem.subItems // is ComplexSidebarItem here 
} 

或者我们可以使用一个函数来创建一个数组,该数组将推断一个元组类型,对于该类型,特定索引处的类型是已知的:

function createItems<T extends SidebarItems>(...a:T){
    return a;
}
const items = createItems({title: '', url: ''}, {title: '', subItems: [{title: '', url: ''}]});
const shouldBeComplexSidebarItem = items[1];
shouldBeComplexSidebarItem.subItems // is an object literal compatible with ComplexSidebarItem

您还可以手动指定元组类型,在这种情况下,不再需要StrictUnion

const items: [SimpleSidebarItem, ComplexSidebarItem] = [{title: '', url: ''}, {title: '', subItems: [{title: '', url: ''}]}];
const shouldBeComplexSidebarItem = items[1];
shouldBeComplexSidebarItem.subItems // is ComplexSidebarItem