Reputation: 2610
What I have done
My initial approach is to use the following mixin that returns a class decorator:
function createMixin (behaviour: any, sharedBehaviour: any = {}): any {
const instanceKeys: any = Reflect.ownKeys(behaviour)
const sharedKeys: any = Reflect.ownKeys(sharedBehaviour)
const typeTag = Symbol(`isa`)
function _mixin (clazz: Function): Function {
for (let property of instanceKeys) {
Object.defineProperty(clazz.prototype, property, {
value: behaviour[property],
writable: true
})
}
Object.defineProperty(clazz.prototype, typeTag, { value: true })
return clazz
}
for (let property of sharedKeys) {
Object.defineProperty(_mixin, property, {
value: sharedBehaviour[property],
enumerable: sharedBehaviour.propertyIsEnumerable(property)
})
}
Object.defineProperty(_mixin, Symbol.hasInstance, {
value: (i: any) => !!i[typeTag]
})
return _mixin
}
export const customUtil = createMixin({
customUtil (event: any) {
console.log(this)
}
})
So later the util can be used to decorate the class and can access this of the target class without any problem:
import { customUtil } from 'utils'
@customUtil
export default class ComponentClass extends Vue {
someClassMethod() {
this.customUtil() // successfully outputs the whole class in the console
}
}
But it results in the tslinter warning TS2339: Property 'customUtil' does not exist on type 'ComponentClass'.
Questions
1. Is there a possibility to resolve the linter issue by somehow "typing" the method assigned to the class by the mixin utility
2. If there is an alternative approach to have utility functions/classes that will have no issue to access this of the class they are used in and with a simple way of attaching them?
Upvotes: 0
Views: 339
Reputation: 5418
This issue has been discussed in other Threads and is also a pending issue in the TypeScript github. But there are two ways to get around this.
1: Typecasting
You could just typecast this
to either forget the class Context or enhance it with the nescessary information.
import { customUtil } from 'utils'
@customUtil
export default class ComponentClass extends Vue {
someClassMethod() {
(this as any).customUtil(); // Now the linter will be fine but you will lose type safety
(this as any as {customUtil: (event:any) => void}).customUtil(); // This works and you could/should extract the type.
}
}
But as you can see, this is not ideal.
2: Real TypeScript Mixins
You could use real TypeScript Mixins instead of Decorators:
Utils
type Constructor<T = {}> = new (...args: any[]) => T;
// The Mixin
export function WithUtils<TBase extends Constructor>(Base: TBase) {
return class extends Base {
customUtil (event?: any) { // Actually, 'event' needs to be optional
console.log(this)
}
};
}
// A Base class so you can just extend if needed
export const UtilsBase = WithUtils(class {});
Component
export default class ComponentClass extends WithUtils(Vue) {
someClassMethod() {
this.customUtil() // successfully outputs the whole class in the console
}
}
Class not extending from Vue
export default class SomeClass extends UtilsBase {
someClassMethod() {
this.customUtil() // successfully outputs the whole class in the console
}
}
// alternatively, you can use the Mixin with an unnamed class
export default class SomeClass extends WithUtils(class {}) {
someClassMethod() {
this.customUtil() // successfully outputs the whole class in the console
}
}
Upvotes: 1