See #1380
Some design question regarding MobX 5
There are two functions on observable arrays that work differently in observable arrays compared to real arrays. These are reverse and sort, which don't modify the original array, but rather return a new array to which the transformation has been applied (with real arrays, the arrays are updates in place)
So question (A) what should we do with them:
@computed get sorted() { return this.array.sort() throw, as it would try to update this.array)this.array.slice().sort() or this.array.replace(this.array.slice().sort()) depending on his intentions.Observable objects can now divided in four categories:
I think that for example observable _class instances_ (created using @observable / extendObservable or decorate) should be in category 1. That is, they will remain working as in MobX 4. Don't convert new fields automatically to observables. Also, classes are very inpractical to proxy; at least the class itself should be decorated as well
On the other hand objects created through obserable.object (or observable(obj)) should be proxied. We can safely do that as we can avoid, unlike with classes, leaking references to the backing instance (the proxy target).
The question, however is, should these objects also by dynamic?
Detect property additions and removes, and be able react to them. So that we can do things like:
const x = observable({})
autorun(() => {
console.log(x.y)
})
x.y = 3
However, there are also some disadvantages to dynamic objects. First, probably not all fields should be changeable / removable. Think of for example @computed, @action etc based fields.
Secondly, making objects dynamic is expensive, as we need to either introduce an additonal atom to track changes to the keyset, or an atom _per key_ to track the existens of a key (this is what observable maps do as well). Consequences:
const x = observable({})
autorun(() => {
console.log(x.y)
})
x.z = 3
If there is just an atom to track key changes, this addition of the z attribute will trigger the autorun. Which is probably very confusing.
If we would keep track of key existence, we double the amount of atoms per object and the number of atoms that are tracked by any reaction.
Question (B): track key existence or just keySet changes? I think the latter has less cognitive overhead (no unexpected reruns). (FYI: arrays are single atom, and x[2] does actually react to x[3] = "something")
We could introduce separate notions of observable.object and observable.record, where the latter works with a fixed, predefined key set (as .object in MobX 4) which is much more efficient, and keep .object as the easy default.
Alternatively, we could deprecate .object and replace it with .record and .dynamic, where .dynamic is dynamic, but doesn't support things like actions, computed fields etc. It becomes really just a data collection.
Finally, what does @observable field = { x : 3 } for it's value. Will it create a dynamic collection or a record?
Question (C): what methods should observable... have for creating (non)dynamic objects
Question (D): should dynamic objects support all the other types of properties
Question (E): what type of objects should @observable infer for plain objects?
cc @spion @urugator @jamiewinder @mattruby
Arrays should probably always be dynamic:
const x = observable([])
autorun(() => {
console.log(x.length) // this needs to run
});
x[0] = 3;
Although if possible, maybe only for adding numeric values and for .length being observed.
Just my opinion:
Question (A): I think we should align with native arrays and warn if they're not being .sliced (so option 3). The current behavior is a caveat and removing it is a big win for MobX imo.
Question (B): I think that if the object is dynamic, less unexpected autoruns is less cognitive overhead. So I agree with you here.
Question (E): I think that non-dynamic, since I want to control what properties are observed and which are not. In practice in 99% of cases you declare your classes with @observable field. So while the array caveat comes up a lot with new users and you have to deal with it in practice - I've found that dynamic key addition is something people figure out once (when they realize how mobx works) and that's it. No strong feelings about it though.
A) Align with native
B) Ideally key existence, but is it really so expensive?
Don't we "just" need to define new observable prop lazily inside reaction on unininitialized prop read?
Ideally such prop should be "temporal", meaning:
C) D) E) Ideally there should be only single dynamic object supporting (re)definition of anything :) But since I am not entirely familiar with actual technical limitations, I dunno.
Btw I am no sure if it worths introducing new type (dynamic object), just for the sake of proxies...
You know if "dynamic object" means object limited to key-value pairs (no action/computed etc) and being expensive, then you have a map with a different syntax ... is there really any added value?
Side note about naming: I would simply go with dynamicObject and staticObject ...
In case a seperate, more expensive implementation is inavoidable, can't we swap the underlaying implementation dynamically, when some uninitialized prop is accessed inside reaction? (similary to what JS does with arrays)
Observable arrays - I think alignment with native is the way to go. Any deviations from native behaviours will be tomorrow's gotchas. The quirks mentioned can be easily worked around once you recognise the problem.
Observable objects - I've only ever used class instance observables in practice, never plain objects, so I'm probably not well placed to comment on this. My gut feeling is that proxied + dynamic is the best default simply because it inline with how JavaScript properties seem to work (every unknown property === undefined until set - I know there is more to it than that, but I don't think the intricacies of this have much bearing on MobX).
@jamiewinder
Observable objects - I've only ever used class instance observables in practice, never plain objects, so I'm probably not well placed to comment on this.
My gut feeling is that this applies to 98%+ of people using MobX.
Yeah I think good points are brought up here, make observable.objects dynamic and flexible as possible. If performance is critical, you should have gone for classes anyway, which benefit much more from engine optimizations
Agree, make observable.object fully automatic and dynamic. Then, make classes powerful enough (with tools like decorators) for developers to configure anything they want to be observed. And with that power, they can implement dynamics themselves in their classes.
Not sure whether this is the right place to ask: do we expect to see some syncing solution for separated mobx stores (remote stores) in future version of mobx? Probably integrate ideas from solutions like: https://github.com/automerge/automerge
Nope; serialization is a userland problem to solve, to much intertangled
with other architectural decisions which MobX doesn't enforce.
Op di 27 mrt. 2018 om 12:17 schreef Guan Gui notifications@github.com:
Not sure whether this is the right place to ask: do we expect to see some
syncing solution for separated mobx stores (remote stores) in future
version of mobx? Probably integrate ideas from solutions like:
https://github.com/automerge/automerge—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx/issues/1390#issuecomment-376472901, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABvGhGVDstPKcK1H1a3qhuS6qWqrs26_ks5tihGmgaJpZM4Sl-Nl
.
Serializr or MST or 2 of the many options that exist for that
Op di 27 mrt. 2018 om 12:21 schreef Michel Weststrate <[email protected]
:
Nope; serialization is a userland problem to solve, to much intertangled
with other architectural decisions which MobX doesn't enforce.Op di 27 mrt. 2018 om 12:17 schreef Guan Gui notifications@github.com:
Not sure whether this is the right place to ask: do we expect to see some
syncing solution for separated mobx stores (remote stores) in future
version of mobx? Probably integrate ideas from solutions like:
https://github.com/automerge/automerge—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/mobxjs/mobx/issues/1390#issuecomment-376472901, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABvGhGVDstPKcK1H1a3qhuS6qWqrs26_ks5tihGmgaJpZM4Sl-Nl
.
Syncing is more difficult than simply se/deserialising. Imagine two users simultaneously changing two initially identical mobx stores, and how can we keep them in sync with minimum effort? Yes, I agree it is out of current mobx’s scope, because it only deals with uni-directional data flow, while syncing is dealing with bi-directional data flow. BUT it will be super cool if two obervable target can be kept in sync :P
@guiguan check out MobX State Tree (MST) michel mentioned - it's his take at how that particular problem should be (sometimes) solved and it's pretty useful: https://www.youtube.com/watch?v=xfC_xEA8Z1M
MobX 5 can now be tried!
[email protected]
It is a release candidate and I don't expect much changes anymore (we are using it already internally), changelog needs to be polished, and docs need to be updated to reflect the changes (volunteer welcome!)
would be nice to have changelogs posted here.
See https://github.com/mobxjs/mobx/pull/1380/files#diff-4ac32a78649ca5bdd8e0ba38b7006a1e
MobX 5 has been released :) https://medium.com/@mweststrate/mobx-5-the-saga-continues-4852bce05572
Most helpful comment
MobX 5 has been released :) https://medium.com/@mweststrate/mobx-5-the-saga-continues-4852bce05572