【问题标题】:Type inference in typescript classes打字稿类中的类型推断
【发布时间】:2018-08-12 09:37:00
【问题描述】:

我希望 arg 参数具有从父类推断的类型

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg) {
    return true
  }
}

但实际上编译器会抱怨 arg 的隐式类型为 any。

我也尝试过使用界面的方法

export interface IEngine {
  viewer?: (arg: string) => boolean
}

export class MyClass implements IEngine {
  viewer(arg) {
    return true
  }
}

编译器认为 arg 的类型是 any 也有同样的问题。

为什么类型推断在这里不起作用?我该怎么做才能让它发挥作用?

【问题讨论】:

  • 据我所知,Typescript 在这种情况下不会推断参数类型。

标签: typescript type-inference


【解决方案1】:

你当然可以推断! Typescript 拥有有史以来最强大的通用系统!
它只需要一些神秘的语法。

你可以这样写(check it on Typescript Playground):

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg: IEngineClas["viewer"] extends (arg: infer U) => any ? U : any) {
    return true
  }
}

let test = (new MyClass()).viewer("hop") // Type OK
let test2 = (new MyClass()).viewer(1)    // Wrong type

解释:

IEngineClas["viewer"] 可以检索你的父函数的类型:(arg:string) => boolean

使用conditional types,您可以通过使用infer 关键字将其分配给泛型来检索所需的arg。

这样读:如果IEngineClas["viewer"]的类型是(arg: U) => any(带参数的函数),则获取U的类型(第一个参数),并将其作为参数@987654331的类型@。否则,使用类型any

编辑

一种更好的编写方式,使用 type (check it on Typescript Playground):

type firstArg<T> = T extends (arg: infer U) => any ? U : any

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg: firstArg<IEngineClas["viewer"]>) {
    return true
  }
}

let test = (new MyClass()).viewer("hop") // Type OK
let test2 = (new MyClass()).viewer(1)    // Wrong type

原因

在另一种情况下,有一天我问为什么这些与抽象类有关的预期推理行为不是默认的,得到的回答是由于性能问题。而且我承认,在大型项目中,Typescript 变得非常缓慢。即使在抽象类上激活或不激活类型的编译标志也会受到欢迎。

我问的帖子:https://github.com/Microsoft/TypeScript/issues/21428

啊,还有……

如果您只想摆脱 implicit any 警告,只需明确指定 any 类型:viewer(arg:any),或禁用 noImplicitAny 标志编译器选项。

【讨论】:

  • 呃..这是避免写string的一个很长的路要走,更不用说你仍然需要知道函数的参数数量(猜测OP不是在寻找一个单一的参数解决方案)。我会用条件类型来归档这个unde fun,但实际上并不使用:)
  • @TitianCernicova-Dragomir 我承认我无法想象这个用例,但这个示例可能是真实场景的简化版本。如果抽象函数类型可能会有所不同(作为函数方法的结果,类型可以演变),那么它是完全有意义的。
【解决方案2】:

这里没有类型推断。成员类型不是由父类或实现的接口推断的。 arg 不是推断的 string 而是隐含的 any

子类有机会覆盖方法签名,只要它与父方法兼容。可以将方法定义为viewer(arg: any) {...}。由于stringany, 的子集,因此将允许这样做,而viewer(arg: boolean) {...} 则不会。

viewer(arg) {...}arg 产生隐含的any,与viewer(arg: any) {...} 相同。它将在松散编译器模式下工作,但会导致strictnoImplicitAny 编译器选项出现类型错误。 noImplicitAny 特别有助于避免在这种情况下意外推断出 any

【讨论】:

  • 在这种情况下,有没有办法让编译器推断string 而不是any?这就是我想要实现的理想行为。
  • 我希望它以与您描述的相同的方式工作,但不,这就是 TS 已经工作的方式。如果存在允许更改基本行为的编译器选项,这将非常具有误导性。如果您想避免由any 引起的意外错误,请坚持使用 noImplicitAny。
  • 另一方面,对作为函数参数传递的对象的类型推断是有效的。
  • @wkrueger 我没有看到你对这个问题进行了奖励。如果我理解正确你的意思,函数参数中的类型推断不是一回事。从技术上讲,当前的行为是正确的。我不确定推断这样的类型是否是一件好事,因为目前尚不清楚它应该如何严格地执行方法实现。如果viewer 实现是viewer(arg, arg2) {} 而不是viewer(arg) {} 怎么办? TS 应该将类型推断为 viewer(arg: string, arg2: any) {} 还是对签名不同感到不安?
【解决方案3】:

没有很好的方法来做到这一点。您将得到的最接近和最简单的方法是,如果您 implement 一个接口(或一个类,抽象类也可以实现,但是您丢失了共享代码)是使用函数字段并将其键入为:

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass implements IEngineClas {
  viewer: IEngineClas['viewer'] = function (this: MyClass, arg) { // we need to be explicit about who this is
    return true;
  }
}

这样做的缺点是函数被分配给每个实例而不是原型,你也使用super

如果您对此有强烈的感觉,我会提出关于 GitHub 项目的建议。

【讨论】:

  • 这种情况下的相关问题似乎是microsoft/TypeScript#6118,在已实现的类属性中实现上下文类型的尝试失败。可能应该这样做,但如果设置此限制,显然太多的实际代码会中断。
猜你喜欢
  • 1970-01-01
  • 2018-06-09
  • 1970-01-01
  • 1970-01-01
  • 2020-04-02
  • 2020-12-20
  • 2016-08-03
  • 2019-04-10
  • 1970-01-01
相关资源
最近更新 更多