I have a TS code pattern that I find very useful but it is extremely not DRY. Any ideas how to fix it?
I like to "link" together a TS interface
with a class
declaration. In this way I have the advantage of some simple typed data-structure with inheritance, AND I can easily do type checks at runtime with a instanceof
operator (that allows me to avoid type predicates, which I find unsafe).
For an example see the code below, where I have a class Doctor
which extends a base class Person
interface PersonInterface {
id: number
name: string
surname: string
}
class Person implements PersonInterface {
id: number
name: string
surname: string
constructor(arg: PersonInterface) {
this.id = arg.id
this.name = arg.name
this.surname = arg.surname
}
}
interface DoctorInterface extends PersonInterface {
degree: string
salary: number
}
class Doctor extends Person implements DoctorInterface {
degree: string
salary: number
constructor(arg: DoctorInterface) {
super(arg)
this.degree = arg.degree
this.salary = arg.salary
}
}
const doc = new Doctor({
id: 111,
name: 'John',
surname: 'Johnson',
degree: 'PHD',
salary: 100000,
})
console.log(doc instanceof Person) // true
console.log(doc instanceof Doctor) // true
Everything works, the type check is easy and my IntelliSense is happy. All great. But as you can see I am repeating myself 3 times for each class. One time to declare the interface
, another to implement it in the class
and a final one to apply the constructor
.
Isn't there a more coincise way? In a big project this becomes horrible to look at.
IMPORTANT I do not need to have methods in my classes. They are used only to rapresent data, not behaviour (in fact I am using them to populate a vuex store)
CodePudding user response:
First of all, there is no magic syntax to make this nicer.
But it is worth noting that a class can be used as an interface:
class Person {
id: number
name: string
surname: string
constructor(arg: Person) {
this.id = arg.id
this.name = arg.name
this.surname = arg.surname
}
}
// works
const personData: Person = { id: 123, name: 'Al', surname: 'Bundy' }
const person: Person = new Person(personData)
You may think that is a bit odd, but the plain object and the instance of Person
both have the exact same public interface, so they are considered compatible.
IMPORTANT I do not need to have methods in my classes. They are used only to rapresent data, not behaviour (in fact I am using them to populate a vuex store)
So why are you using classes at all? Then there's no need. A plain object that conforms to that interface is more or less indistinguishable from the instance of a class that implements that interface, so you can save a lot of boilerplate with just:
interface Person {
id: number
name: string
surname: string
}
const person: Person = { id: 123, name: 'Al', surname: 'Bundy' }