typescript中的Spread运算符不';我不能保证类型正确

typescript中的Spread运算符不';我不能保证类型正确,typescript,spread-expression,Typescript,Spread Expression,我正在使用spread操作符创建一个具有更改字段的副本,如下面的示例()。但是,如果在创建对象文本时更改了字段的类型,编译器不会抱怨: class A{ constructor(readonly a: number, readonly b: string[]){} copy(): A { // must return an object of type A return { ...this, b: 99 // sho

我正在使用spread操作符创建一个具有更改字段的副本,如下面的示例()。但是,如果在创建对象文本时更改了字段的类型,编译器不会抱怨:

class A{
    constructor(readonly a: number, readonly b: string[]){}
    copy(): A { // must return an object of type A
        return {
            ...this,
            b: 99 // should be of type string[]!
        }
    }
}

const a: A = new A(1, ["x", "y"])
const a1 = a.copy()
console.log(JSON.stringify(a1))
输出:

[LOG]: "{"a":1,"b":99}"
创建显式接口也无济于事:

interface IA {
    readonly a: number
    readonly b: string[]
}

...

copy(): IA { ...
看起来它与数组无关,因为下一个示例也会编译:

class A{
    constructor(readonly a: number, readonly b: number){}
    copy(): A {
        return {
            ...this,
            b: "my string" // should be of type number!
        }
    }
}

为什么这在TypeScript中是可能的?还是一个编译器错误?有没有办法避免

我不知道确切的解释,但发生这种情况的原因是
这种
的类型

A类{
构造函数(只读a:number,只读b:string[]){}
copy():A{//必须返回类型为A的对象
常量对象={
这
b:99//应为字符串[]类型!
}
返回{
这
b:99//应为字符串[]类型!
}
}
}
在上述代码中,
this
的类型是
this
,而不是
A
。因此对象
obj
的类型是-

this&{
b:数字;
}
这也是返回对象的类型,并且可以以某种方式分配给
A
(我不知道原因)

解决方案是在函数签名中添加此类型

A类{
构造函数(只读a:number,只读b:string[]){}
copy(this:A):A{//必须返回类型为A的对象
//       ^^^^^^^^^
常量对象:A={
这
b:99//应为string[]!//类型错误
}
返回{
这
b:99//错误
}
}
}

这是以下类型系统事实之间的交互作用:

  • 这个
    实际上是多态的
    这个
    类型。多态
    是扩展当前类的类的隐式泛型类型参数。(见最后一段)
  • 如果展开操作涉及泛型类型的操作数,则结果是展开的项的类型的交集
  • 交叉点可分配给其任何组成部分。因此,例如,类型
    A&B
    可分配给
    A
    B
    ,而不考虑
    A
    B
    ()的任何属性之间的任何不兼容(参见分配兼容性)
  • 应用这3条规则,我们得到
    {…this,b:99}
    的类型为
    this&{b:number}
    ()。可分配给
    A
    ,因为
    扩展了A

    例如,如果您将assert
    this
    键入
    A
    ,实际上会得到一个错误,因为不涉及泛型操作数的扩展操作是正确键入的()

    如果这是一个bug,我会称之为设计限制。很长一段时间以来,将扩展与泛型操作数一起使用是不可能的。这项功能是在3.2()中添加的,阅读PR中的注释,我们可以清楚地看到团队意识到此处的一些漏洞:

    使用交叉点的另一种选择是沿着所研究的路线引入高阶类型运算符
    {…T,…U}
    。虽然这在一开始看起来很吸引人,但要实现这个新类型构造函数并赋予它我们已经为交叉口类型实现的所有功能需要大量的工作,而且对于大多数场景来说,它在精度方面几乎不会或根本不会产生任何增益。特别是,只有当排列表达式涉及具有不同类型的重叠属性名称的对象时,差异才真正重要。此外,对于不受约束的类型参数
    T
    ,类型
    {…T,…T}
    实际上不可分配给
    T
    ,这在技术上是正确的,但会令人讨厌


    添加了上面的重点,您的用例就属于这个问题。

    在添加用类键入的
    时,让编译器正确地检查类型(如Shivam Singla的建议),它并不能解决所有问题,因为

    因此,我在所有方法中都进行了显式的
    new
    调用

    class A{
        constructor(readonly a: number, readonly b: string[]){}
        copy(): A {
            return new A(this.a, 99)
        }
    }
    
    该方法可以通过一个参数对象进行优化,如果构造函数有许多参数,但只有一个或几个参数需要更改,这一点尤其有用。以下示例显示了此方法:

    interface PointParams {
      readonly x?: number
      readonly y?: number
    }
    
    class Point{
      constructor(readonly x: number, readonly y: number) {}
      copy(params: PointParams): Point {
        return new Point(params.x ?? this.x, params.y ?? thhis.y)
      }
    }
    

    我也试过
    Object.assign
    ,但是扩展表达式只是语法上的糖分,所以没有任何变化。

    上使用显式类型注释这是一个非常好的主意。我也愿意接受你的答案,因为键入是解决方案,而@TitianCernicova Dragomir的答案解释了为什么会发生这种情况。@deamon没有问题。我很高兴,这有帮助。:)非常感谢@Titan:)回答StackOverflow问题真的是学习东西的好方法。这是一个打字错误吗-加速项目-扩展项目?一个后续问题-既然
    这个
    扩展了当前类,那么为什么TS不解决
    这个扩展了类?true:false
    就像在
    类型X='abc'扩展字符串中一样?true:false
    @ShivamSingla通常不会解析仍然包含未解析类型参数的条件类型,即使结果是明确的。让类型从类中转义,然后得到解析。前任: