类实现接口时 Typescript 不正确的类型推断

Typescript incorrect type inference when class implements interface

提问人:Charlie Scene 提问时间:10/6/2019 最后编辑:Charlie Scene 更新时间:11/16/2023 访问量:2434

问:

当在打字稿中使用组合方法而不是继承方法时,我想根据它们“可以”而不是“是什么”来描述我的实体。为了做到这一点,我需要创建一些复杂的接口,然后为我的类(我使用类是为了不创建手动原型链,并且不破坏我认为存在于 js 引擎中的一些优化)来实现我的接口。但是,当未正确推断方法类型时,这会导致奇怪的行为。相反,当使用对象并声明它们属于相同的接口类型时,一切都按预期工作。

所以我将 VSCode 与打字稿 3.6.3 一起使用。我已经为2d形状创建了界面,该界面应该具有将所有法线返回到边缘的方法。然后我创建实现该接口的类,我希望它需要这个方法,它应该具有相同的返回类型(这部分有效)和相同的参数类型(这个没有)。参数被推断为 any。我的问题是我不想手动创建原型链只是为了获得一致的 VSCode 行为。

此外,在控制台中运行 tsc 时,我收到相同的错误,即参数在类方法中为“any”类型,并且在访问不存在的 prop 时在对象方法中出现预期错误

interface _IVector2 {
  x: number;
  y: number;
}

interface _IShape2D {
  getNormals: ( v: string ) => _IVector2[];
}

export class Shape2D implements _IShape2D {
  getNormals( v ) {
    console.log( v.g );
                   ^ ----- no error here

    return [{} as _IVector2];
  }
}

export const Test: _IShape2D = {
  getNormals( v ) {
    console.log( v.g );
                   ^------ here we get expected error that  
                   ^------ 'g doesn`t exist on type string'

    return [{} as _IVector2];
  }
};

我的tsconfig.json

{
  "compilerOptions": {
    "target": "es2017",
    "allowSyntheticDefaultImports": true,
    "checkJs": false,
    "allowJs": true,
    "noEmit": true,
    "baseUrl": ".",
    "moduleResolution": "node",
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noFallthroughCasesInSwitch": true,
    "jsx": "react",
    "module": "commonjs",
    "alwaysStrict": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "noErrorTruncation": true,
    "removeComments": true,
    "resolveJsonModule": true,
    "sourceMap": true,
    "watch": true,
    "skipLibCheck": true,
    "paths": {
      "@s/*": ["./src/*"],
      "@i/*": ["./src/internal/*"]
    }
  },
  "exclude": [
    "node_modules"
  ]
}

预期:
- 类方法的参数应推断为字符串
实际:
- 方法的参数推断为任意

最终,我的问题如下: “这种行为在ts中是无法实现的,我应该求助于手写(哦,亲爱的......原型链和原型的简单对象?

先谢谢你!

TypeScript 方法 接口实现

评论

0赞 D Pro 10/6/2019
方法的签名由其名称和参数组成,因此,仅当您指定相同的参数类型(在本例中为 string)时,typescript 才会知道您正在从接口实现该方法。
0赞 Charlie Scene 10/6/2019
好吧,即使我没有指定任何参数,它实际上也正确地推断了返回类型。那么根据这个能力,为什么它不能“理解”参数类型应该是什么呢?

答:

3赞 jcalz 10/7/2019 #1

这是 TypeScript 中的设计限制(请参阅 ms/TS#1373)。ms/TS#6118 尝试修复,但它与现有真实世界代码有一些不良/破坏性的交互,因此他们放弃了它ms/TS#32082 上有一个未解决的问题,要求提供更好的东西,但目前没有任何有用的东西。

此时的建议是在实现/扩展类中手动注释参数类型;这比求助于手写的原型链要好,尽管更烦人。

export class Shape2D implements _IShape2D {
  getNormals(v: string) { // annotate here
    console.log(v.g); // <-- error here as expected
    return [{} as _IVector2];
  }
}

请注意,这确实可以是或将是一个正确的实现:vanyunknowngetNormals()

export class WeirdShape implements _IShape2D {
  getNormals(v: unknown) { // okay
    return [];
  }
}

这是由于方法参数逆变是类型安全的...a 仍然是一个完全有效的 .因此,虽然将参数推断为会很好,但更通用并没有什么不正确的。WeirdShape_IShape2Dstring

无论如何,希望这会有所帮助;祝你好运!

代码链接

评论

0赞 Charlie Scene 10/7/2019
感谢您的回复。我有一些想法,我不可能是第一个发现这种“错误”的人,你的答案解释得很好。但我想确保我做对了一切。这个用例在类实例上推断方法类型时有一些问题,但是当我们处理属性恰好是函数的具体对象时,就没有这样的问题了。为什么会这样?另外,我不确定如果我明确说该参数是字符串,那么是否应该允许将其设置为 any。其他方式 - 当然,但不像你描述的那样
0赞 jcalz 10/7/2019
上下文类型发生在显式注释的对象上,但不会发生在类方法上,因为后者从未实现过,当他们尝试时,它会破坏人们的代码太多。我认为这只是历史上的侥幸,而不是原则上的差异。仍然有一些希望这将得到解决。
0赞 jcalz 10/7/2019
看到这个
0赞 Augusto Franzoia 11/16/2023 #2

作为解决方法,您可以使用实用程序类型从另一个接口提取参数。当使用单个对象作为函数参数(相对于位置参数)并且您的方法的参数可能经常更改时,这很有用。Parameters

例如:

interface Foo {
  method(args: { arg1: string, arg2: number}): void;
}

class Bar implements Foo {
  method({ arg1, arg2 }: Parameters<Foo['method']>[0]): void {
    // arg1 and arg2 types will be correctly inferred
  }
}

如果有多种方法可以重新声明,则可以为特定类创建自己的实用程序类型:

interface Foo {
  method(args: { arg1: string, arg2: number}): void;
}

type FooArgs<Method extends keyof Foo> = Parameters<Foo[Method]>[0];

class Bar implements Foo {
  method({ arg1, arg2 }: FooArgs<'method'>): void {
    // arg1 and arg2 types will be correctly inferred
  }
}

希望能有所帮助。