Skip to content

Instantly share code, notes, and snippets.

@mweststrate
Last active February 21, 2019 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mweststrate/8a4d12db0e11ca536c9ff3b6ba754243 to your computer and use it in GitHub Desktop.
Save mweststrate/8a4d12db0e11ca536c9ff3b6ba754243 to your computer and use it in GitHub Desktop.
Decorator usage in mobx
// case 2: manipulates the value of the descriptor, returns fresh descriptor
class MyClass {
@action someMethod() {
}
}
function action(target, key, descriptor) {
const baseValue = descriptor.value
return {
...descriptor,
value: function() {
// some stuff
return baseValue.apply(this, arguments)
}
}
}
// case 3: turns descriptor in getter setter
class MyClass {
@boundAction someMethod() {
}
}
function boundAction(target, key, descriptor) {
return {
get() {
return descriptor.value.bind(this)
// - OR hide this prototype prop with the bound method:
this[key] = descriptor.value.bind(this) // (and make it non-enumerable, so use define, details.)
return this[key]
}
}
}
// case 1: messes a bit with the prototype, doesn't do anything really fance
@observer(class SomeReactComponent {
})
// implementation
function observer(target) {
const baseRender = target.prototype.render
target.prototype.render = function() {
// Some stuff
return baseRender.apply(this, arguments)
}
return target
}
// case 4: turns descriptor into interceptable prop
class MyClass {
@observable prop = 3
}
const x = new MyClass()
x.hasOwnProperty("prop") // Babel: false(!), TypeScript: true
x.prop // triggeres getter
x.hasOwnProperty("prop") // TypeScript: true, TypeScript: true
// Implementation:
function observable(target, key, descriptor) {
// defineObservableProp will hide the observable prop in the prototype, upon the first time it is accessed.
// observable introduces a dummy get/set property on the prototype, which whole purpose is to install a property on the actual instance
// (for correct ownership)
return {
get() {
// Babel decorators-legacy stores initializer expression on the descriptor:
// (TS decorators don't need this, they always start with calling the setter in the constructor, due to [[Set]] field initializers)
const initialValue = descriptor.initializer && descriptor.initializer.call(this)
defineObservableProp(target, key, initialValue)
return this[key] // will invoke the property define in the line above
},
set(value) {
defineObservableProp(target, key, value)
}
}
}
function defineObservableProp(target, key, value) {
Object.defineProperty(target, key, {
get() {
trackingSystem.notifyRead(this, key)
return this.internalMap.get(key)
},
set(value) {
this.interlMap.set(key, value)
trackingSystem.notifyWrite(this, key)
}
})
}
// Problem 1: `prop` get's installed on the prototype, not on the instance, so reflection methods (getOwnProperties) will skip `prop`
// -> current solution with legacy decorators: install the property on 'this' upon the first read or write
// -> For TypeScript: this works perfect, as [[set]] semantics cause this to be triggered in the constructor
// -> For Babel: results in subtle bugs; the property doesn't show up when for() looping the object, until a first read / write was made to that object!
// See below:
// Problem 2: with `[[define]]` semantics for initializers: the setter will never be called? (or how will it influence the descriptor?)
// Summary: Both the current TS implementation, and the stage 3 proposal, make it possible to have the decorator instantiate properties on instances
// However, the babel legacy-decorator implementation doesn't offer this flexibility.
Possible solution:
However, if a decorator + field initializer would run during object instantion, rather than during class defintion time, this could be address. E.g.:
class A {
@decorator field() { } // decorator called with (A, "field", { value: function() {} }), during class declaration time
@decorator field = 3 // decorator called with (this, "field", { value: 3 } }), just before constructor runs
}
// The different moments of invocation might look weird at first. But it could be described as: the decorator is run for on the owner of the descriptor being initialized
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment