Currently if I create a class that implements an interface, the class created will all methods not included in the interface. Here's an example:
interface ExampleTypes {
alpha();
}
class Example implements ExampleTypes {
alpha () {
return true
}
beta () {
return true
}
}
I am looking for a way to restrict the methods a given class can have.
This is something I also tried:
class ExampleSource {
alpha () {
return true
}
}
class Example implements Partial<ExampleSource> {
alpha () {
return true
}
beta () {
return true
}
}
And this:
class ExampleSource {
alpha () {
return true
}
}
class Example implements ExampleSource {
alpha () {
return true
}
beta () {
return true
}
}
Which is unintuitive. I'd like beta
to not be allowed in Example
.
This is the functionality that works but using a function and not a class:
interface ExampleType {
alpha?();
beta?();
}
This is value:
function Example(): ExampleType {
return {
alpha: () => true,
};
}
This throws a typescript error:
function Example(): ExampleType {
return {
alpha: () => true,
meow: () => true,
};
}
Ideally I can have this same functionality but with classes.
Currently if I create a class that implements an interface, the class created will all methods not included in the interface. Here's an example:
interface ExampleTypes {
alpha();
}
class Example implements ExampleTypes {
alpha () {
return true
}
beta () {
return true
}
}
I am looking for a way to restrict the methods a given class can have.
This is something I also tried:
class ExampleSource {
alpha () {
return true
}
}
class Example implements Partial<ExampleSource> {
alpha () {
return true
}
beta () {
return true
}
}
And this:
class ExampleSource {
alpha () {
return true
}
}
class Example implements ExampleSource {
alpha () {
return true
}
beta () {
return true
}
}
Which is unintuitive. I'd like beta
to not be allowed in Example
.
This is the functionality that works but using a function and not a class:
interface ExampleType {
alpha?();
beta?();
}
This is value:
function Example(): ExampleType {
return {
alpha: () => true,
};
}
This throws a typescript error:
function Example(): ExampleType {
return {
alpha: () => true,
meow: () => true,
};
}
Ideally I can have this same functionality but with classes.
Share Improve this question edited Dec 7, 2018 at 18:55 ThomasReggi asked Dec 7, 2018 at 18:21 ThomasReggiThomasReggi 59.4k97 gold badges257 silver badges459 bronze badges 10 | Show 5 more comments2 Answers
Reset to default 27It's an odd request, since having extra methods won't stop you from using the class as if they weren't there. TypeScript doesn't really have a lot of support for excluding extra properties or methods from types; that is, there's currently no direct support for exact types as requested in microsoft/TypeScript#12936.
Luckily you can sort of get this behavior by making a self-referential conditional, mapped type:
type Exactly<T, U> = { [K in keyof U]: K extends keyof T ? T[K] : never };
If you declare that a type U
is Exactly<T, U>
, it will make sure U
matches T
, and that any extra properties are of type never
. Self-referential/recursive/circular types don't always compile, but in this case you're only referring to keyof U
inside the definition of U
, which is allowed.
Let's try it:
interface ExampleTypes {
alpha(): boolean; // adding return type
}
// notice the self-reference here
class Example implements Exactly<ExampleTypes, Example> {
// okay
alpha() {
return true;
}
// error! Type '() => boolean' is not assignable to type 'never'.
beta() {
return true;
}
}
Looks like it works!
I found the answer by @jcalz to be extremely useful, however it breaks the behavior of "implements" because it only checks in one direction. For example, the following code does not report any errors, even though the Example class does not implement the interface.
Here's a Playground
type Exactly<T, U> = { [K in keyof U]: K extends keyof T ? T[K] : never };
interface IExample {
foo: string;
}
// This should have an error but does not
class Example implements Exactly<IExample, Example> {}
The solution is to check both the class and interface to make sure all properties are present and that there are no additional properties:
Here's a Playground
type Exactly<Interface, Klass> = {
[K in keyof Interface]: K extends keyof Klass ? Interface[K]: never;
} & {
[K in keyof Klass] : K extends keyof Interface ? Interface[K] : never;
};
interface IExample {
foo: string;
}
// Error - "foo" not implemented
class Example implements Exactly<IExample, Example> {}
class Example2 implements Exactly<IExample, Example2> {
foo!: string;
bar!: number; // Error - "bar" not assignable to "never"
}
final
in TS – skyboyer Commented Dec 7, 2018 at 18:26Partial<ExampleSource>
. But to actually try to restrict what methods a class contains is really suspect. – Mark Peters Commented Dec 7, 2018 at 18:40