【问题标题】:Type 'A | B' is not assignable to type 'A & B'类型'A | B' 不可分配给类型 'A & B'
【发布时间】:2021-08-25 09:45:01
【问题描述】:

为什么这段代码无法编译?

type A = { n: number }
type B = { s: string }
type Thing = {
  a: A
  b: B
}
function update(obj: Thing, path: keyof Thing) {
  obj[path] = obj[path]
}

我希望分配的双方都具有A | B 类型,但 TypeScript 编译器失败:

error TS2322: Type 'A | B' is not assignable to type 'A & B'.
  Type 'A' is not assignable to type 'A & B'.
    Property 's' is missing in type 'A' but required in type 'B'.

10     obj[path] = obj[path]
       ~~~~~~~~~

有没有办法让它工作?

【问题讨论】:

    标签: typescript static-typing union-types


    【解决方案1】:

    这是由于 Typescript 3.5 中引入了changes 以提高索引访问类型的可靠性。

    当索引访问 T[K] 发生在类型关系的源端时,它会解析为由 T[K] 选择的属性的联合类型,但是当它发生在类型关系的目标端时,它现在解析为由 T[K] 选择的属性的交集类型。以前,目标端也会解析为联合类型,这是不合理的。

    type A = { n: number }
    type B = { s: string }
    type Thing = { 
      a: A
      b: B
    }
    
    declare const thing: Thing
    
    const key = 'a' as keyof Thing
    
    let t = thing[key] // t has type A | B
    t = { s: '' }      // we can `safely` assign it a value of type `B`
     
    thing[key] = t     // and here things break
    

    playground link


    有关工作示例,请参阅此answer

    【讨论】:

      【解决方案2】:

      当您访问obj[path] 时,它可能返回AB,编译器不知道是哪个,并且它(正确地)不允许赋值,除非满足所有约束:

      type key = keyof Thing;
      const keys: key[] = ['a', 'b'];
      
      const path = keys[Math.round(Math.random())];
      const thing: Thing = {
          a: {
              n: 3
          },
      
          b: {
              s: 'hi'
          }
      };
      
      const result = thing[path]; // A | B, compiler can't know which!
      

      所以现在如果我使用路径访问事物,我会得到 A 还是 B?编译器无法知道,也无法在访问点缩小类型。你我都知道这是一回事,但编译器无法证明。 funarg 也是如此。

      如果你使用静态可知的东西,编译器允许赋值:

      function update(obj: Thing) {
        obj.a = obj.a;
        obj['a'] = obj['a'];
      }
      

      Playground

      这涵盖了为什么,但如果您想知道如何让编译器理解正确的类型,请查看this canonical answer

      【讨论】:

      • 谢谢。我了解A | B 类型,但A & B 类型从何而来?
      • @Tamlyn 因为编译器不知道它是 A 还是 B,为了允许赋值,您必须满足 both 类型的结构约束,因此A&B。例如,您可以分配{n: 3, s: 'hi'},因为该对象符合条件,所以可以使用。有趣的是(我不确定为什么会这样)适用于我的答案,但不适用于 nghiepit。我可能不得不问一个单独的问题!
      【解决方案3】:

      您可以使用接受任何键的索引签名,其值为A & B 的交集。

      type A = { n: number }
      type B = { s: string }
      type AB = A & B;
      type Thing = {
        [key: string]: AB;
      }
      function update(obj: Thing, path: keyof Thing) {
        obj[path].n = 0;
        obj[path].s = "hello";
      }
      

      【讨论】:

      • 这没有回答所提出的问题,这就是编译器不允许赋值的原因。此外,这会丢失类型信息!
      【解决方案4】:

      试试这个

      function update<T extends Thing>(obj: T, path: keyof T) {
        obj[path] = obj[path]
      }
      

      【讨论】:

      • 这可行,但不能回答所提出的问题,这就是编译器不允许赋值的原因。
      猜你喜欢
      • 2019-09-25
      • 2021-05-15
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-25
      • 2020-12-25
      • 2022-11-07
      相关资源
      最近更新 更多