TypeScript TS提案:"接口错误地扩展了接口" - 子接口方法重载或覆盖?

zynd9foi  于 5个月前  发布在  TypeScript
关注(0)|答案(7)|浏览(73)

TypeScript 版本: 2.6.2

问题

初始场景

昨天我尝试给一个接口继承链添加一些功能时遇到了这个问题。这个问题的背景是想象出来的,比实际需要展示的问题要长,但我想提供一个完整的示例来清楚地表明需要解决这个问题。
所以,让我们想象一个测试应用程序,它应该对设备进行仪器化:以下是一个潜在的情况模型:

interface Stream
{
}
interface InputStream
{
}
interface OutputStream
{
}
// interface inheraitance is a great feature
interface IOStream extends InputStream, OutputStream
{
}
interface InterfaceLike
{
}
interface DeviceInterfaceLike extends InterfaceLike
{
    init();
}
interface InputDeviceInterfaceLike extends DeviceInterfaceLike
{
    input(); // this should word just like the `touch` command
             //     like if there's and error with the device, it will trigger exception
    input(stream: InputStream);
}
interface ScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    input(stream: InputStream, coordinates: {x: number, y: number});
}
interface TouchScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    input(stream: IOStream, coordinates: {x: number, y: number});
}

预期行为:

它应该只是在子接口中重载那个方法。

实际行为:

error TS2430: Interface 'ScreenDeviceInterfaceLike' incorrectly extends interface 'InputDeviceInterfaceLike'.
  Types of property 'input' are incompatible.
    Type '(stream: InputStream, coordinates: { x: number; y: number; }) => any' is not assignable to type '{ (): any; (stream: InputStream): any; }'.
core.ts(73,11): error TS2430: Interface 'TouchScreenDeviceInterfaceLike' incorrectly extends interface 'InputDeviceInterfaceLike'.
  Types of property 'input' are incompatible.
    Type '(stream: IOStream, coordinates: { x: number; y: number; }) => any' is not assignable to type '{ (): any; (stream: InputStream): any; }'.
18:38:26 - Compilation complete. Watching for file changes.

它要求我在每个接口中完全复制粘贴所有方法签名。

interface ScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    input(); // this should word just like the `touch` command
             //     like if there's and error with the device, it will trigger exception
    input(stream: InputStream);
    input(stream: InputStream, coordinates: {x: number, y: number});
}
interface TouchScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    input(); // this should word just like the `touch` command
             //     like if there's and error with the device, it will trigger exception
    input(stream: InputStream);
    input(stream: IOStream, coordinates: {x: number, y: number});
}

当你可以通过继承链分布多达(为什么不呢)15个重载时... 它变得(是的,你说对了)庞大。
现在,编译器还不能确定何时 override 继承自父类的方法签名,何时 overload 它们。

建议

我的第一个想法是引入一个新的关键字,就像 @ts-nocheck@override 一样。

TouchScreenDeviceInterfaceLike

然后,@overload 接口将变成:

interface TouchScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    @override   // it is now clear that the only way to instrument a touchscreen is to provide a `IOStream` and coordinates (`{x: number, y: number}`)
    input(stream: IOStream, coordinates: {x: number, y: number});
}

此外, @override 当默认行为是重载时是有用的,但我们仍然可以定义默认行为以 在子接口中重定义方法时抑制所有父类定义,除非使用 @overload。也就是说

interface InputDeviceInterfaceLike extends DeviceInterfaceLike
{
    // to override all definitions from parents
    init(istream: InputStream);
}

或者

interface InputDeviceInterfaceLike extends DeviceInterfaceLike
{
    @overload // to overload signatures from parents
    init(istream: InputStream);
}

TouchScreenDeviceInterfaceLike 接口可以变成:

interface TouchScreenDeviceInterfaceLike extends InputDeviceInterfaceLike
{
    @overload                           // to overload signatures from parents
    {
        init(istream: InputStream);     //      except this signature... So that it remains only the empty parameter and the below
    }
    input(stream: IOStream, coordinates: {x: number, y: number});
}

通过仔细思考,我认为第一步应该是允许继承类型默认重载方法。

= = = = = = 更新 = = = = = =

我现在认为有必要补充一点,绝对不是通过 @Override 在接口继承链的任何地方完全擦除(删除、移除)一个方法。... 甚至不通过 @Override!因为这可能会(将会)破坏OOP继承的核心概念。这个机制是为了确保只在给定的继承节点处理(接受)某些方法(签名)。换句话说,如果 @Override 抑制了所有签名,那么至少必须有一个方法签名。

ycggw6v2

ycggw6v21#

这个问题也适用于被重写的枚举。

interface BaseType
{
    allowedSounds: 'BARK' | 'MOO';
}
interface DerivedType extends BaseType
{
    allowedSounds: 'BARK' | 'MOO' | 'CAW';
}

这目前会导致以下错误:

Interface 'DerivedType' incorrectly extends interface 'BaseType'.
  Types of property 'allowedSounds' are incompatible.
    Type '"BARK" | "MOO" | "CAW"' is not assignable to type '"BARK" | "MOO"'.
      Type '"CAW"' is not assignable to type '"BARK" | "MOO"'.

但从语言的Angular 来看,能够在派生类型中重写枚举是一个完全合法的期望。

vbkedwbf

vbkedwbf2#

但是从语言的Angular 来看,能够在派生类型中覆盖枚举是完全合法的期望。这是一个正确的错误。DerivedType 不是 BaseType ,因为通过 BaseType 引用查看 DerivedType 的代码不期望看到值 CAW

kiayqfof

kiayqfof3#

@RyanCavanaugh实际上,经过深思熟虑,我同意你的观点。我自己对此感到矛盾,但还是写下了这些。

s3fp2yjn

s3fp2yjn4#

我现在认为有必要补充一点,绝对不是说通过接口继承链,一个方法就应该完全被擦除(删除、移除)。甚至连 @Override 都不能这么做!因为这可能会(将)破坏OOP继承的核心概念。
这个机制的目的是确保在给定的继承节点上只处理(接受)某些方法的签名。换句话说,如果 @Override 抑制了所有签名,那么至少需要一个方法签名。

t40tm48m

t40tm48m5#

当我仔细思考时,我意识到这将与一个名为Override的终端用户装饰器冲突。尽管anykeyof和其他一些字符串被保留为TypeScript关键字...我建议一个替代方案。

@!Override 或者只是 !Override(为什么不呢)

这不会产生冲突,因为它目前是一个语法错误。

nue99wik

nue99wik6#

我有一些类似于以下的React属性:
IValidationTextAreaProps extends IAutoSizeTextAreaProps extends HTMLProperties<HTMLTextArea>
使用三个不同的名称来表示 onChange 监听器真的很烦人。
HTMLTextareaonChange: (event) => boolean
AutoSizeTextAreaonValueChange: (newVal) => void
而我不得不想出另一个人为的名称来表示ValidationTextAreaProps...可能是
valueChangeListener: (newVal, isValid) => void
希望没有人会尝试使用 onChangeonValueChange
建议:
完全从TypeScript编译器中移除它,只将其作为linting错误

// ts-lint:disable:no-interface-overrides
ISomeInterface extends { name: string} {
  name: string[],
}

或者,如果这看起来太疯狂了,这些可能会起作用:

ISomeInterface extends { name: string} {
  !name: string[],
}

或者

ISomeInterface extends { name: string} {
  name: string[] !allow-override,
}

任何事情都可以,只要TypeScript不会阻止这个JavaScript功能。

fzwojiic

fzwojiic7#

也许我们可以使用自定义类型来解决这个问题:

type ProtoExntends<T, U> = U & {
  [P in Exclude<keyof T, keyof U>]: T[P];
};

我发现使用内置类型的方法更简单:

type ProtoExntends<T, U> = U & Omit<T, keyof U>;

以下是示例用法:

interface Parent {
  aaa: number;
  bbb: number;
  // .
  input(stream: {}): void;
  allowedSounds: 'BARK' | 'MOO';
}
interface ChildExtends {
  aaa: string;
  ccc: string;
  // .
  input(stream: {}, coordinates: {x: number; y: number}): void;
  allowedSounds: 'BARK' | 'MOO' | 'CAW';
}
type Child = ProtoExntends<Parent, ChildExtends>;

从截图来看,一切正常:

相关问题