When instantiating an object I much prefer the following format:
const MyTest = new Test({
title: 'hello';
});
over
const MyTest = new Test('hello');
especially when there are a lot of properties to pass.
I tried to set this up using the following interface and class definitions:
interface ITest {
title: string;
readonly titlePlusCheese: string;
}
class Test implements ITest {
public title: string;
constructor(args: ITest) {
this.title = args.title;
}
get titlePlusCheese(): string {
return `${this.title} CHEESE`;
}
}
However, when calling const MyTest = new Test({ title: 'hello' });
I get the following error:
Property 'titlePlusCheese' is missing in type '{ title: string; }' but required in type 'ITest'.ts(2345)
However, the following works:
interface ITest {
title: string;
readonly titlePlusCheese: string;
}
class Test implements ITest {
public title: string;
constructor(title: string) {
this.title = title;
}
get titlePlusCheese(): string {
return `${this.title} CHEESE`;
}
}
const MyTest = new Test('hello');
which leads me to suspect I'm doing something silly.
Can anyone shed any light on it?
CodePudding user response:
ITest
is the type of just the params you're passing in, not of the class itself. It does not need titlePlusCheese
in it. Instead, put this directly in the class and that is all the typing you need. Your getter does this for you and defines titlePlusCheese
as an implicitly readonly property of the class because there's no setter. Like this:
interface ITest {
title: string;
}
class Test {
public title: string;
constructor(args: ITest) {
this.title = args.title;
}
get titlePlusCheese(): string {
return `${this.title} CHEESE`;
}
}
This should show you why your second version works. It broadly does the same thing as your version implementing ITest, which is to incorporate titlePlusCheese
in the class definition rather than the interface.
It is probably worth reading a bit more about the difference between interfaces and classes. I searched and found these useful snippets:
When use a interface or class in Typescript
https://blog.logrocket.com/when-how-use-interfaces-classes-typescript/
Here's a more extended version inspired by Frank's comment to show the difference between using an interface to shape the constructor arguments and another one to specify what the class must implement:
interface IArgs {
title: string;
}
interface IClass {
title: string;
reasonly titlePlusCheese: string
}
class Test implements IClass {
public title: string;
constructor(args: IArgs) {
this.title = args.title;
}
get titlePlusCheese(): string {
return `${this.title} CHEESE`;
}
}
CodePudding user response:
I'm not sure if I'm following exactly what you are trying to achieve there, but sounds a bit weird, from an OOP perspective, using the interface that defines the shape of a class as the arguments type for the class constructor sounds like an error.
Interface definition could (and usually does) contain methods, abstract properties, etc. that you obviously cannot provide on the construction stage.
That being said, if you really prefer that syntax of new Test({title: 'test'})
(personally I don't, find it confusing and prone to any sort of errors, also isn't going to scale for a bigger number of args), you can either inline the constructor type like constructor({title}: { title: string})
or use something like constructor({ title}: Pick<ITest, "title>)
, both, far from readable.