【问题标题】:Why does TypeScript assertion of object literal `{a}` work with interface `{a, b}` but not `{a?, b}`为什么 TypeScript 对对象字面量“{a}”的断言适用于接口“{a, b}” 而不是“{a?, b}”
【发布时间】:2016-08-07 17:59:40
【问题描述】:

为什么以下断言有效:

interface AllRequired {
    a: string;
    b: string;
}

let all = {a: "foo"} as AllRequired; // No error

但是这个断言给出了一个错误:

interface SomeOptional {
    a?: string;
    b: string;
}

let some = {a: "foo"} as SomeOptional; // Error: Property 'b' missing

我能看到的唯一区别是将接口属性之一设为可选 (?)。似乎如果所有属性都不是可选的,我可以向接口断言部分对象,但是一旦任何接口属性是可选的,我就不能再断言部分对象了。这对我来说真的没有意义,我一直无法找到这种行为的解释。这是怎么回事?


对于上下文:我 encountered this behavior 在尝试解决 React 的 setState() 采用部分状态对象的问题时,但 TypeScript doesn't yet have partial types 使其与您的状态接口正常工作。作为一种解决方法,我想出了setState({a: "a"} as MyState) 并发现只要接口MyState 字段是all 非可选的,但只要一些 属性就会失败可选的。 (将所有属性设为可选是一种解决方法,但在我的情况下非常不可取。)

【问题讨论】:

  • 说实话,我认为 stackoverflow 上只有一个人能够回答这个问题,那就是 @RyanCavanaugh(希望这会召唤他)。否则 TypeScript github 页面上可能会出现问题。

标签: reactjs typescript


【解决方案1】:

类型断言只能用于类型和它的子类型之间的转换。

假设您声明了以下变量:

declare var foo: number | string;
declare var bar: number;

注意numbernumber | string子类型,这意味着任何匹配number 类型的值(例如3)也匹配number | string。因此,允许使用类型断言在这些类型之间进行转换:

bar = foo as number; /* convert to subtype */
foo = bar as number | string; /* convert to supertype (assertion not necessary but allowed) */

同样,{ a: string, b: string }{ a: string } 的子类型。任何匹配{ a: string, b: string } 的值(例如{ a: "A", b: "B" })也匹配{ a: string },因为它有一个a 类型为string 的属性。

相比之下,{ a?: string, b: string }{ a: string } 都不是另一个的子类型。 一些值(例如{ b: "B" })仅匹配前者,而其他值(例如{ a: "A" })仅匹配后者。

如果您真的需要在不相关的类型之间进行转换,您可以通过使用常见的超类型(例如any)作为中间体来解决这个问题:

let foo = ({ a: "A" } as any) as { a?: string, b: string };

规范中的相关部分是4.16 Type Assertions:

在 e 形式的类型断言表达式中,e 由 T 上下文类型化(第 4.23 节),并且 e 的结果类型必须可分配给 T,或者 T 是需要可分配给 e 的结果类型的扩展形式,否则会发生编译时错误。

【讨论】:

    【解决方案2】:

    嗯,这只是一个理论,但我有一些代码可以支持它。

    在您的第一个示例中,当转换为 AllRequired 时,您基本上是在对编译器说您知道自己在做什么,稍后您将设置 all.b 的值。

    在第二个示例中,您认为您正在做同样的事情,但编译器可能会认为“嘿,如果他设置了可选属性,他必须设置一个完整的对象”,因此对于为什么 some.b 的值感到困惑此完整对象中缺少。

    这听起来有点牵强,但在这里:

    let some2 = (all.a ? {a: "foo"} : {}) as SomeOptional
    

    如果您使用条件,错误就会消失,因为(嗯,可能)some2.a 确实是可选的。

    playground 中尝试。

    【讨论】:

    • 如果你也传递了一个必需的 props 部分,那么断言就会失败,例如 interface {a?:string, b:string, c:string} 不能断言 {b:"hi"},但是如果使 a 非可选(所以 all i> 是必需的)然后 same 断言有效。这就像仅仅存在一个可选字段就会将断言踢到一个更严格的检查中。
    • 我不明白你的意思,你的例子没有错误(分配{b:"hi"}没有错误),但是如果我添加a: "yo"然后又会出现错误,这可以用我的建议来解释。
    • 你说得对,我认为{b} as {a?, b, c} 不起作用,但实际上它只有在包含可选参数时才不起作用,例如{a, b} as {a?, b, c},但{a, b} as {a, b, c} 可以工作。所以共同的方面是提供一个 optional 属性使编译器想要 all 非可选属性或其他东西。我不确定我是否理解你的三元示例中发生了什么,它似乎让完整的垃圾通过像 (true ? {a: 123, abc: "foo"} : {}) as SomeOptional 这是一个完全混乱的语句和值(a 是错误的类型,abc 不存在)...
    • 但是这个(true ? {a: 123, abc: "foo"} : { a: "hey" }) as SomeOptional 行不通。基本上,如果您在第一个赋值中包含一个可选字段,那么编译器会要求您包含所有非可选字段。
    • 嗯,(true ? {a: 123, abc: "foo"} : {a: "hey"}) 确实 捕捉到“abc 不是道具”,但它仍然无法捕捉到“a 不是数字”,因为它通常会没有三元。似乎三元左侧的东西比右侧或本身做的事情更加偶然......使用||运算符会出现类似的行为......这对我来说非常困惑!
    猜你喜欢
    • 2014-03-29
    • 2010-12-08
    • 2018-11-22
    • 2021-10-06
    • 2019-10-27
    • 2021-09-10
    • 2011-05-30
    • 1970-01-01
    • 2020-04-13
    相关资源
    最近更新 更多