-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved observable api #1365
Improved observable api #1365
Conversation
src/api/extendobservable.ts
Outdated
fail( | ||
`'extendObservable' can only be used to introduce new properties. Use 'decorate' to update existing properties. The property '${key}' already exists on '${target}'` | ||
) | ||
if (isComputed(value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't throw, but simply assign boxed computed:
const o = {}
mobx.extendObservable(o, {
boxedComputed: mobx.computed(() => "a"),
});
console.log(o.boxedComputed.get());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would create an observable references to a computed object, it is barely imaginable that that is what the user would intend, so throwing and telling how to properly do it is better imho.
If he really needs a boxed computed, he can just do o.boxedComputed = mobx.computed()
. Or in the weird case hey needs an observable ref to it, extending it with null
first, and then assigning it would work fine.
If you try to re-decorate prop (via |
src/api/extendobservable.ts
Outdated
defineObservablePropertyFromDescriptor(adm, key, descriptor!, defaultEnhancer) | ||
const decorators: any = {} | ||
for (let key in properties) { | ||
// TODO: using decorators is a bit inefficient, short circuit those |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you mean to directly define observable on target
based on decorator type, then it can't be used on prototypes
...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that we can't work with prototypes in the same way as with objects directly. It is kinda stupid, but the iterability will be wrong, if you define a property with getter / setter on the prototype, the properties won't be interated by most tools that iterate objects, because they are not own properties of the instances. So JSON.stringify
, console.log
, Object.keys
all will behave weird.
Hence the decorator version is much more complex then the non-decorator implementation. Basically; it creates a property on the prototype / object, that, upon first read, will create a new property definition on this
.
In the implementation in this branch I only used the decorator based implementation to test the new api, but it is effectively twice as slow. This is also the reason decorate
and extendObservable
will both remain as api; extendObservable
(and observable.object
) can short circuit that process because they should always be used on instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In pseudo code, it is something like this:
function decorator(target, prop) {
Object.defineProperty(target, prop, {
enumerable: true,
configurable: true,
get() {
return this._x
},
set(v) {
// on first write, redeclare the prop (also do on first read btw)
Object.defineProperty(this, prop, {
enumerable: true,
get() {
return 3
},
set(v) {
this._x = v * 2
}
})
this._x = v * 2
}
})
}
class X {
@decorator y = 3
}
const x = new X()
console.dir(JSON.stringify(x))
I have some ideas on how that can be improved though, but not sure yet if it will work out :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
N.B, note that the above still has weird behavior;
const x = new X()
Object.getOwnPropertyNames(x) // empty set!
The reason is; before the property is read or written once, it does't exist on the instance yet. This is luckily a problem that will be fixed in the new decorators proposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I think I understand that, but basically it's just a peformance problem ... if target
is not prototype, then "enumberable" getters are defined directly on the object, therefore are "own" and enumerable... and if target is prototype then props are not "owned" by instance until first get ... therefore it doesn't introduce any new problems ...
So, if I understand correctly, after optimization, the decorators passed to extendObservable
won't be actually called... therefore the depedency on decorators is technically redundat and for example strings/symbols could be used instead...?:
extendObservable(o, values, {
a: "ref",
b: "deep",
})
Do you have an example test case? |
const o = {};
mobx.extendObservable(o, {
a: 5,
});
mobx.extendObservable(o, {}, {
a: mobx.observable.ref // a is getter at this point
}); I tried it in that codesandbox you provided with updated dep to alpha2 |
@urugator that is not supposed to work; |
Well ok, but can we do something about the error...? |
yes, good one |
correct, what matters is the 'enhancer', which get's passed to the
observable value when constructing the final property
Op do 1 mrt. 2018 om 21:34 schreef urugator <[email protected]>:
… ***@***.**** commented on this pull request.
------------------------------
In src/api/extendobservable.ts
<#1365 (comment)>:
> const adm = asObservableObject(target)
startBatch()
try {
- for (let key in properties)
- if (hasOwnProperty(properties, key)) {
- if ((target as any) === properties && !isPropertyConfigurable(target, key)) continue // see #111, skip non-configurable or non-writable props for `observable(object)`.
- const descriptor = Object.getOwnPropertyDescriptor(properties, key)
- defineObservablePropertyFromDescriptor(adm, key, descriptor!, defaultEnhancer)
+ const decorators: any = {}
+ for (let key in properties) {
+ // TODO: using decorators is a bit inefficient, short circuit those
Yea I think I understand that, but basically it's just a peformance
problem ... if target is not prototype, then "enumberable" getters are
defined directly on the object, therefore are "own" and enumerable... and
if target is prototype then props are not "owned" by instance until first
get
... therefore it doesn't introduce any new problems ...
So, if I understand correctly, after optimization, the decorators passed
to extendObservable won't be actually called... therefore the depedency
on decorators is technically redundat and for example strings/symbols could
be used instead...?:
extendObservable(o, values, {
a: "ref",
b: "deep",
})
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#1365 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABvGhMGuNQMvL7RLm68nJ1qndU_tFT6Gks5taFtMgaJpZM4SYXC0>
.
|
Improved observable api