基于嵌套对象内部属性的打字稿联合

时间:2020-05-11 14:28:02

标签: typescript typescript-typings react-typescript

我正在尝试基于对象中的嵌套属性创建联合类型。请参见下面的示例:

type Foo = {
    abilities: {
        canManage: boolean
    }
}

type Bar = {
    abilities: {
        canManage: boolean
    }
    extraProp: number
}

type Condition1 = {
    abilities: {
        canManage: true
    }
} & Bar

type Condition2 = {
    abilities: {
        canManage: false
    }
} & Foo

type TotalData = Condition1 | Condition2

const data: TotalData = {
    abilities: {
        canManage: false, // if canManage is false, TS should complain when I add the `extraProp` key
    },
    extraProp: 5
}

我遇到的问题是打字稿会忽略我设置的条件。我有兴趣仅在canMange值为true时才允许某些属性。嵌套时,这似乎不起作用。但是,如果我只有这样的东西而没有嵌套,那就没问题了:

type Foo = {
     canManage: boolean
}

type Bar = {
    canManage: boolean
    extraProp: number
}

type Condition1 = {
    canManage: true
} & Bar

type Condition2 = {
    canManage: false
} & Foo
]
type TotalData = Condition1 | Condition2

const data: TotalData = {

canManage: false,
extraProp: 5 // now typescript complains that this property shouldn't be here because canManage is false
}

当尝试基于嵌套对象内的属性设置Union时,如何解决此问题?

1 个答案:

答案 0 :(得分:1)

编译器不理解“嵌套区分联合”的概念。如果某个类型的工会成员拥有共同的“区分”财产,则该类型就是受歧视的工会。判别属性通常是单例/文字类型,例如true"hello"123甚至是nullundefined。但是,您不能使用另一个有区别的联合作为判别本身。如果可以的话,这会很好,因为有区别的联合可以按照您的操作方式从嵌套属性中传播出来。 microsoft/TypeScript#18758上有一个建议允许这样做,但是我看不到那里有任何动静。

就目前而言,类型TotalData并不是 discriminated 联合。这只是一个工会。这意味着编译器将不会尝试将TotalDataCondition1的类型Condition2视为排他。因此,如果编写测试data.abilities.canManage并期望编译器理解其含义的代码,您可能会遇到问题:

function hmm(x: TotalData) {
    if (x.abilities.canManage) {
        x.extraProp.toFixed(); // error!
    //  ~~~~~~~~~~~ <--- possibly undefined?!
    } 
}

如果要执行此操作,您可能会发现自己需要写user-defined type guard functions来代替:

function isCondition1(x: TotalData): x is Condition1 {
    return x.abilities.canManage;
}

function hmm(x: TotalData) {
    if (isCondition1(x)) {
        x.extraProp.toFixed(); // okay!
    } 
}

您在这里遇到的特定问题,其中data被视为有效的TotalDataexcess property checking的执行方式有关。 TypeScript中的对象类型是“打开” /“可扩展”,而不是“关闭” /“ exact”。您可以添加类型定义中未提及的其他属性,而不会违反类型。因此,编译器无法完全禁止多余的属性;取而代之的是,它使用启发式方法尝试找出这些属性何时是错误的以及何时是故意的。使用的规则主要是:如果您正在创建全新的对象文字,并且未在使用该对象的类型中提及其任何属性,则会出现错误。否则就不会有。

如果TotalData是一个有区别的联合,您将在data上遇到您期望的错误,因为data.abilities.canManage会导致编译器将data的范围从{{1} }到TotalData,其中未提及Condition2。但是事实并非如此,因此extraProp仍然是data,而确实提到了TotalData

microsoft/TypeScript#20863中,有人建议对不歧视的工会严格检查多余的财产。我几乎同意;来自不同工会成员的混合和匹配属性似乎并不是常见的用例,因此警告可能会有所帮助。但是,这又是一个长期存在的问题,我在那里看不到任何动静。


为此,您可以做的一件事就是更加明确地说明您要防止的多余属性。类型extraProp的值可以具有类型{a: string}的{​​{1}}属性,但是类型b的值不能。因此,后一种类型将防止类型string的属性,而无需依赖编译器的启发式方法进行过多的属性检查。

在您的情况下:

{a: string, b?: never}

的行为与原始的b定义非常相似,但是现在出现此错误:

type Foo = {
    abilities: {
        canManage: boolean
    };
    extraProp?: never
}

编译器无法再与Fooconst data: TotalData = { // error! // -> ~~~~ // Type '{ abilities: { canManage: false; }; extraProp: number; }' // is not assignable to type 'TotalData'. abilities: { canManage: false, }, extraProp: 5 } 协调data,因此会抱怨。


好的,希望能有所帮助;祝你好运!

Playground link to code