mix-classes
Version:
Seamlessly combine class inheritance with composition, guaranteed to work with any class
166 lines (124 loc) • 3.71 kB
Markdown
# mix-classes
Easily add typescript-safe mixins to JS classes, with support for generics, constructors, overloading and more. Correctly handles `this` for each class, so it'll work with anything.
- Typescript generics
- Pass arguments to mixins, by providing an array of arguments
- Supports `super` calls in overloaded methods
- Use [`instanceof`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/instanceof) to check for mixin classes
- Handles the `this` inside classes, so that they always access their local scope first. No need to worry about name-collisions
```ts
import { Mix } from 'mix-classes'
class Contactable {
constructor(public email: string, public phone?: string) {}
}
class Nameable {
constructor(public name: string) {}
}
class Website {
constructor(public websiteUrl: string) {}
}
class Developer extends Mix(Nameable, Contactable, Website) {
constructor() {
super(['Bob'], ['hi@example.com'], ['https://example.com'])
}
}
class Company extends Mix(Nameable, Contactable) {
constructor() {
super(['Apple'], ['hi@apple.com', '18-00'], ['https://apple.com'])
}
}
const developer = new Developer()
developer.name
developer.email
developer.websiteUrl
const company = new Company()
company.name
company.email
company.phone
company.websiteUrl
```
## Constructor arguments
You can pass custom constructor arguments to each mixin within an array inside the `super` call. The arguments order is dependant on the `mix` array order.
```ts
import { Mix } from 'mix-classes'
class Nameable {
constructor(public name: string) {}
}
class Ageable {
constructor(public age: number) {}
}
class Person extends Mix(Nameable, Ageable) {
constructor() {
super(['Bob'], [50])
// ^ name argument for Nameable
// ^ age argument for Ageable
}
}
```
## Overloading
All mixins are seperate classes with different `this` values, meaning you don't need to worry about name collisions.
```ts
import { Mix, getMixin } from 'mix-classes'
class A {
variable = 'a'
public a() {
return this.variable
}
}
class B {
variable = 'b'
public b() {
return this.variable
}
}
class Test extends Mix(A, B) {
constructor() {
super()
// The default value is the last mixin specified
console.log(this.variable) // 'b'
// Use getMixin to get overloaded properties
console.log(getMixin(this, A).variable) // 'a'
// Mixins retain access to their local variables
this.a() // 'a'
this.b() // 'b'
}
}
const test = new Test()
```
## Typescript generics
Typescript generics are supported, but it requires using Typescript's declaration merging.
To use them, simply wrap the class that you want to pass generics to in `Generic()`, and then add an interface with the same name as the class you want it in.
Before:
```ts
import { Mix } from 'mix-classes'
class MyClass<T extends string = 'initial'> extends Mix(
User,
Nameable,
Ageable
) {}
```
After:
```ts
import { Generic, Mix } from 'mix-classes'
// Move all generic type signatures to the interface
// including default values.
interface MyClass<T extends string = 'initial'> extends User<'bob'> {}
class MyClass<T> extends Mix(Generic(User), Nameable, Ageable) {}
```
```ts
import { Mix, Generic } from 'mix-classes'
class B {}
class Role<Type extends string> {
constructor(public type: Type) {}
}
class User<Username extends string> {
constructor(public username: Username) {}
}
interface Admin extends User<'bob'>, Role<'admin'> {}
class Admin extends Mix(Generic(User), Generic(Role), B) {
constructor() {
super(['bob'], ['admin'])
}
}
const test = new Admin()
test.username // type 'bob'
```