Why can't I "implement" every optional interface in TypeScript 2.4+?

I wrote the code:

interface IEventListener {
    onBefore?(name: string): void;
    onAfter?(name: string): void;
}

class BaseListener implements IEventListener {
    stuff() { 

    }
}

      

The goal is that someone can get from BaseListener

and get the correct type checking in their onBefore

/ methods onAfter

:

class DerivedListener extends BaseListener {
    // Should be an error (name is string, not number)
    onBefore(name: number) {

    }
}

      

However, I am not getting an error in DerivedListener

. Instead, I got an error in BaseListener

:

The "BaseListener" type has no properties in common with the "IEventListener" type

What's happening?

+3


source to share


1 answer


The proposal implements

in TypeScript does only one thing: it ensures that the declaration class is assigned to the implemented interface. In other words, when you write class BaseListener implements IEventListener

, TypeScript verifies that this code is legal:

var x: BaseListener = ...;
var y: IEventListener = x; // OK?

      

So when you wrote class BaseListener implements IEventListener

, what you probably intended to do is "copy" the optional properties IEventListener

into your class declaration.

Instead, nothing happened.

TypeScript 2.4 has changed how all optional types work. Previously, any type that did not have properties of the conflicting type would be assignable to the optional type. This leads to the resolution of all types of fraud:



interface HttpOptions {
  method?: string;
  url?: string;
  host?: string;
  port?: number;
}
interface Point {
  x: number;
  y: number;
}
const pt: Point = { x: 2, y: 4 };
const opts: HttpOptions = pt; // No error, wat?

      

The new behavior since 2.4 is that an all-optional type requires at least one matching property from the source type for a type that is considered compatible. This catches the above error and also correctly detects that you've tried the implements

interface without actually taking any action.

Instead, you should use a declaration that combines the "copy" of interface members into your class. It's as simple as writing an interface declaration with the same name (and parameters of the same type, if any) as a class:

interface IEventListener {
    onBefore?(name: string): void;
    onAfter?(name: string): void;
}

class BaseListener {
    stuff() { 

    }
}
interface BaseListener extends IEventListener { }

      

This will cause the properties to IEventListener

also be in BaseListener

and correctly flag the error in DerivedListener

the original post.

+4


source







All Articles