Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up🚀 Proposal: MobX 6: 🧨drop decorators,😱unify ES5 and proxy implementations, 💪smaller bundle #2325
Comments
|
You probably already know this IMO its time to stand our ground. |
|
Quick thought: |
|
I worry that this data/metadata duality will backfire eventually. If I remember correctly we had something like this in Mobx2.
|
Personally I also hate what TC39 is doing to decorators, and maybe it is one of those things that should be left to transpilers. Maybe there could be a mobx-decorators v7 package for those that want to keep using with them? (and also could make adoption easier).
Sounds awesome.
As long as there are alternatives it should be ok (mobx-keystone for example relies on both observer, intercept and then some more). But the more changes there are, the more likely current mobx libs will become incompatible and stop adoption.
When is a class considered simple? When there are no fields with mobx "decorators"?
I think 99% of the time you'd want functions to become actions (and actually I didn't even know this observable.ref was the case), so as long as this is explained on some release notes it should be ok. |
mobx-logger depends on |
@spion Frankly, that is just a rant. Michel wrote the pros of removing decorators. If you want to "stand ground" and "strongly advocate", then please make constructive counter-arguments instead of just "I like them". Personally, I am on the other side of this war and I never liked decorators. This fresh new look definitely makes sense to me. However, if there is some possibility to keep decorators support in the external package as it was suggested, then it's probably something we should do. If people feel the necessity to wear a foot gun, it's their choice. Besides, it would be suddenly more apparent where that decorator magic is coming from and that it's something optional. |
This comment was marked as off-topic.
This comment was marked as off-topic.
|
No, being serious here
… April fools @mweststrate <https://github.com/mweststrate> ?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBGYSQOTJNWFY5ZPUYLRKQRTHANCNFSM4LZQ2B5Q>
.
|
Yeah, I think that is the part I like the least as well. Maybe we should introduce a separate 'marker' to for observable properties, e.g. |
|
I like decorators but I totally understand this direction. There are a few reasons for this, let me try and make Gorgi's case @FredyC :
Also, removing something so widely used because the spec committee can't proceed always leaves a bad taste in my mouth. Even as I acknowledge they are all acting in good faith and that decorators are a hard problem for engines because of the reasons outlined in the proposal (I've been following the trapping decorators discussions). |
|
Some API bikeshedding: Decorators class Doubler {
@observable value = 1
@computed get double() {
return this.field * 2
}
@action increment() {
this.value++
}
}Michel's original: class Doubler {
value = 1
get double() {
return this.field * 2
}
increment() {
this.value++
}
constructor() {
autoInitializeObservables(this)
}
}Subclass: class Doubler extends ObservableObject {
value = 1
get double() {
return this.field * 2
}
increment() {
this.value++
}
}Can be interesting, but is not very good in terms of coupling. Or with a class wrapper: const Doubler = wrapObservable(class Doubler {
value = 1
get double() {
return this.field * 2
}
increment() {
this.value++
}
}); |
Don't forget there will be a codemod to make most of the hard work for you, so this isn't really a valid argument. Besides, nobody is forced to upgrade to the next major. It probably depends if we will be able to maintain v4 & v5 or abandon those. And if we separate package will exist for decorators then it might be fairly non-braking change. Btw, @mweststrate Just realized, why MobX 7? Do we need to skip V6 for some reason? |
|
Doh, I can't count.
Subclassing doesn't work, as the fields are not visible to the subclass
when it runs its constructor. Wrapping is probably a bit scary for many
people, but more importantly it is troublesome in TypeScript, as generic
arguments aren't nicely preserved that way, and with circular module
dependencies, as const expressions are not hoisted the same way as classes
IIRC (that is an recurring issue in MST).
…On Thu, Apr 2, 2020 at 10:56 AM Daniel K. ***@***.***> wrote:
- Decorators have been the "mostly standard MobX way" for a while and
removing them is 5 years of breakage.
Don't forget there will be a codemod to make most of the hard work for
you, so this isn't really a valid argument. Besides, nobody is forced to
upgrade to the next major. It probably depends if we will be able to
maintain v4 & v5 or abandon those. And if we separate package will exist
for decorators then it might be fairly non-braking change.
Btw, @mweststrate <https://github.com/mweststrate> Just realized, why
MobX 7? Do we need to skip V6 for some reason?
|
|
@FredyC hey, I would prefer it if we avoided terms like "isn't really a valid argument" when talking about each other's points. I think having a common and standard way to do something in a library (decorators) is definitely a consideration and API breakage is a big concern - even with a codemod. I think removing decorators is unfortunately the way forward - but breaking so much code for so many users definitely pains me. @mweststrate subclassing is also not very ergonomic and mixes concerns here IMO. I'm not sure I understand the wrapping issue in TypeScript but I know there are challenges involving it. Wrapping doesn't actually have to change the type similarly to initializeObservables: class Doubler {
...
}
initializeObservables(Doubler); // vs in the constructorOr even decorate the type in a non-mutating way: const ReactiveDoubler = initializeObservables(Doubler); // vs in the constructorWouldn't it make more sense to initializeObservables on the type and not on the instance? Is there any case I'd want to conditionally make an instance reactive but not have several types? |
|
@benjamingr yeah that is exactly what So even if you don't know the fields, but you do specify them on the type, you can't decorate the type to trap the assignment, because the new property will simply hide it. I think it is still a weird decisions to standardize |
|
@FredyC It is not a rant, it's a constructive comment. Decorators are widely used throughout the community, with large projects such as Angular, NestJS and MobX taking advantage of them. Thousands of projects depend on them. For TC39 to block their standardization process strikes me as extremely irresponsible, and the arguments for doing so are severely under-elaborated (a vague 3-pager does not an elaboration make - try harder TC39). The advantages that @mweststrate mentioned are largely the fault of this lackluster standardization which means the argument is cyclic: it ultimately comes down to "we're not supporting decorators because decorators are not well supported". Language features that aren't yet standardized are never well supported, the argument can be used to justify not adopting any new language feature. So if TC39 "paves cowpaths" and everyone adopted this way of thinking, the language would stop evolving. (Clearly, this is not a new feature so there is some merit to the "not likely to be supported" argument implying its a good idea to give up on them. I just wanted to bring to the table that the other approach - standing our ground - might be good too) For those of us who do care about decorators, what are our options? Our only hope is to stand our ground, keep using them and keep advocating their standardization. Even if TC39 doesn't standardize them, development within TypeScript might continue, addressing the remaining gaps WRT reflection and types. If you don't care about decorators, please stay out of it. They have always been optional in MobX and will continue to be optional - no one is forcing you to use them. If you care about a smaller bundle, they can be offloaded to a side module (but I maintain they should still have a first-class place in the documentation) |
|
@mweststrate is there anything stopping us from trapping construct and intercepting those fields then or setting a proxy? Such an That is:
That way the fact the field is not a part of the type doesn't really matter - since while it looks like we are decorating the prototype/class we are actually decorating the instance and only "decorating" the construct on the type. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
|
Requiring a base class removes flexibility, for example using decorators in
react component classes
Op di 7 apr. 2020 06:45 schreef Michael Lin <notifications@github.com>:
… @mweststrate <https://github.com/mweststrate> Maybe it's fine to use a
proxy for this, but the downside is that the browser must support Proxy.
What do you think about it?
class Observable {
constructor() {
return new Proxy(this, {
defineProperty(target: any, key: any, descriptor: any) {
// handle decorator
},
});
}
}
class Foo extends Observable {
@observable
field1 = 'value1';
field2 = 'value2';
constructor() {
super();
}
}
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBH3JOHOH3TMXU56E23RLK4V5ANCNFSM4LZQ2B5Q>
.
|
|
Indeed, flexibility is reduced. It can only look like this below, and it looks okay. class Foo {
@observable
field1 = 'value1';
field2 = 'value2';
constructor() {
makeObservable(this);
}
}
|
|
@mweststrate |
|
Too early to tell but I don't expect any significant memory gains.
Op za 11 apr. 2020 19:51 schreef Grzegorz Szeliga <notifications@github.com
…:
@mweststrate <https://github.com/mweststrate>
You wrote on Twitter today that the new mobx will be very good. Which is
incredibly good news :)
What will it look like from the user's side?
Will the new version eat less memory?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBHI56FCAMTG43ORPB3RMC32XANCNFSM4LZQ2B5Q>
.
|
|
Have you considered an unopinionated MobX core build? Including just your basic TFRP functions ( That minimal set of functions should be enough to work with MobX React Lite and would let us sidestep the entire ES5 vs ES6 vs decorators question for a decent number of use-cases. |
Oh, no!
My key message is that stores in Mobx is… umm… smart. It can autorun, lazy compute etc so why it can't avaiblity to manipulate of incoming data? |
|
They won't be dropped :-P. I was just wishful thinking :)
…On Thu, Apr 16, 2020 at 4:15 PM Igor «InoY» Zvyagintsev < ***@***.***> wrote:
drop some features like spy, observe, intercept
Oh, no!
I use both intercept and observe:
1. I have a store for statistics data with absolute values *(count of
emails sended, opened etc)* and while I put the data to store the hook
intercept calls a function which calculate relative data *( e.g. %
opened/sended)*
2. Also I have a store Parameters and there I use observe for changing
url of current page after sone parameters changed
My key message is that stores in Mobx is… umm… *smart*. It can autorun,
lazy compute etc so why it can't avaiblity to manipulate of incoming data?
|
Now assuming you mean Typescripts These ideas are bad for two reasons. Regarding
|
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
|
Another strong vote against removing decorators. |
I found it ugly too. Especially as there may be different ways of auto: In a class of mine all getters are computed, all fields but one are observable, but only about a half of functions are actions, others are views (in the MST sense). So I'd use So I guess, the auto feature should be configurable, maybe like Moreover, there probably should be a global |
|
I would leave the I've been thinking whether the [1] Semantically it doesn't make sense to apply |
|
Actually I think it could be simplified like this: function wrap(fn) {
return (...args) => {
// provide batching
startBatch()
// allow reads
allowStateReadsStart()
// !!! untracked is missing so it works with views
// !!! allow state changes only when outside of derivation
if (notInsideDerivation()) {
allowStateChangesStart()
}
const result = fn(...args)
if (notInsideDerivation()) {
allowStateChangesEnd()
}
allowStateReadsEnd()
endBatch()
return result;
}
}As a consequence if the auto decorated function mutates state, it will throw when called from derivation (computed/autorun), so you will be forced to wrap it in makeAutoObservable({
method: action, // now you can call it even from derivation
})or inside derivation: autorun(() => {
runInAction(() => store.anAutoDecoratedMethod())
}) |
|
@urugator yes, I start to see the light! I think you are spot on. Looking back,
I think we can indeed introduce what you are proposing, let me know if this summarizes it correctly: MobX has a global context state (or stack) that is either
* track is the underlying primitive of autorun / computed
|
|
Well basically that goes back to the initial idea - it all sort of depends on how much we are willing to change API, introduce BCs and how far we want to go with these checks. I think you got it mostly right, but: Semantically it's a bit weird that the Throw on write detection to enforce correct context isn't bullet proof: computed(() => {
// this throws so the user is forced to use `runInAction` here
myAction();
})
const myAction = action(() => {
// but actually it throws here, so we can fix it here
runInAction(() => {
observable.set(5);
});
})Potentially we could restrict from which to which context the transition can occur, but then we may need to again differentiate between
There is EDIT: One more concern... Is it possible for |
|
@mweststrate Sorry, I didn't mean to hijack the issue. I was showing my custom element example hoping to discuss similar issues that would happen in MobX. Let me ask smaller MobX-specific questions one at a time: If MobX rolls with the approach of class Doubler {
value = observable(1)
double = computed(function () {
return this.value * 2
})
increment = action(function () {
this.value++
})
constructor() { initializeObservables(this)}
}then we want to extend it, class DoubleIncrementer extends Doubler {
increment = action(function () {
super.increment() // ?
super.increment() // ?
})
}how would we do that considering that In #1864, you said
If MobX gives people a |
|
MobX 6 won't go with that proposal, but the second one
…On Mon, May 4, 2020 at 2:47 AM Joe Pea ***@***.***> wrote:
@mweststrate <https://github.com/mweststrate> Sorry, I didn't mean to
hijack the issue. I was showing my custom element example hoping to discuss
similar issues that would happen in MobX.
Let me ask smaller MobX-specific questions one at a time:
If MobX rolls with the approach of
class Doubler {
value = observable(1)
double = computed(function () {
return this.value * 2
})
increment = action(function () {
this.value++
})
constructor() { initializeObservables(this)}
}
then we want to extend it,
class DoubleIncrementer extends Doubler {
increment = action(function () {
super.increment() // ?
super.increment() // ?
})
}
how would we do that considering that super wouldn't work there?
In #1864 <#1864>, you said
Don't bind methods in super classes, it doesn't mean in javascript what you
hope it means :-)
But if MobX gives people a class-based tool, they expect to be able to use
class features.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBHML3MRNXDQZHSILX3RPYNBZANCNFSM4LZQ2B5Q>
.
|
|
The explicit one like |
|
Yes
…On Mon, 4 May 2020, 07:03 Joe Pea, ***@***.***> wrote:
The explicit one like initializeObservables(this, { foo: observable, bar:
computed, etc })?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBDJANONQFSOPHE3Z3LRPZLEVANCNFSM4LZQ2B5Q>
.
|
|
I suppose private class fields are out of the question, as there is no way to access those dynamically from |
|
Correct
Op zo 10 mei 2020 19:03 schreef Joe Pea <notifications@github.com>:
… I suppose private class fields are out of the question, as there is no way
to access those dynamically from initializeObservables(this, ...)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBF4G6AZKWOGKN4MOQ3RQ3T5JANCNFSM4LZQ2B5Q>
.
|
|
@mweststrate What happened with this idea to tackle that? |
|
The last comment was about JS privates, not TS privates
…On Mon, 11 May 2020, 01:08 Seivan, ***@***.***> wrote:
@mweststrate <https://github.com/mweststrate> What happened with this idea
<#2339 (comment)> to
tackle that?
@trusktr <https://github.com/trusktr>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2325 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN4NBD6I5ARFPRIXUU2YVTRQ46WBANCNFSM4LZQ2B5Q>
.
|
|
I think the example with |
Yep, but in the constructor to overcome [[Define]] semantics of class fields. Maybe that's best: it remains familiar and without performance cost of "auto initialize" (though an auto can be convenient in cases where the perf hit doesn't matter). |
|
To clarify the original post didn't reflect anymore the new api of V6, instead we opted to implement the alternative api as found in the original post as discussed earlier in this thread. I just updated the post to better reflect the new api. So we won't be using value wrappers, but rely on |


MobX 6
Hi folks, I've tinkered a lot about MobX 6 lately, so I want to layout the vision I have currently
Goals
Let's start with the elephant in the room.
I think we have to drop the support for decorators.
Some have been advocating this for years, others totally love decorators.
Personally I hate to let decorators go. I think their DX and conciseness is still unparalleled.
Personally, I am still actively engaged with TC-39 to still make decorators happen, but we are kinda back to square one, and new proposal will deviate (again) from the implementation we already have.
Dropping decorators has a few advantages, in order of importance (imho)
[[define]]over[[set]]semantics, all our decorator implementations (and thedecorateutility) are immediately incompatible with code that is compiled according the standard. (Something TypeScript doesn't do, yet, by default, as for TS this is a breaking change as well, unrelated to decorators). See #2288 for more backgrounddecorate). I expect to drop a few KB by simply removing them.The good news is: Migrating a code base away from decorators is easy; the current test suite of MobX itself has been converted for 99% by a codemod, without changing any semantics (TODO: well, that has to be proven once the new API is finalized, but that is where I expect to end up). The codemod itself is pretty robust already!
P.s. a quick Twitter poll shows that 2/3 would love to see a decorator free MobX (400+ votes)
I'd love to have MobX 6 ship with both Proxy based and ES5 (for backward compatibility) implementations. I'm not entirely sure why we didn't combine that anymore in the past, but I think it should be possible to support both cases in the same codebase. In Immer we've done that as well, and I'm very happy with that setup. By forcing to opt-in on backward compatibility, we make sure that we don't increase the bundle size for those that don't need it.
P.S. I still might find out why the above didn't work in the past in the near future :-P. But I'm positive, as our combined repo setup makes this easier than it was in the past, and I think it enables some cool features as well, such as detection of edge cases.
For example we can warn in dev mode that people try to dynamically add properties to an object, and tell them that such patterns won't work in ES5 if they have opted-in into ES5 support.
By dropping decorators, and making sure that tree-shaking can optimize the MobX bundle, and mangling our source aggressively, I think we can achieve a big gain in bundle size. With Immer we were able to halve the bundle size, and I hope to achieve the same here.
To further decrease the build, I'd personally love to drop some features like
spy,observe,intercept, etc. And probably a lot of our low-level hooks can be set up better as well, as proposed by @urugator.But I think that is a bridge too far as many already rely on these features (including Mobx-state-tree). Anyway I think it is good to avoid any further API changes beyond what is being changed in this proposal already. Which is more than enough for one major :). Beyond that, if goal 2) is achieved, it will be much easier to crank out new majors in the future :). That being said, If @urugator's proposal does fit nicely in the APIs proposed below, it might be a good idea to incorporate it.
4.🛂 Enable strict mode by default
The 'observed' one, that is.
UPDATE 22-5-20: this issue so far reflected the old proposal where all fields are wrapped in instance values, that one hasn't become the solution for reasons explained in the comments below
This is a rough overview of the new api, details can be found in the branch.
To replace decorators, one will now need to 'decorate' in the constructor. Decorators can still be used, but they need to be opted into, and the documentation will default to the non-decorator version. Even when decorators are used, a constructor call to
makeObservable(this)is needed, the type will be picked from the decoratorsmakeAutoObservable(this, exceptions?)that will default to observable for fields, computed for getters, action for functionsProcess
Timeline
Whatever. Isolation makes it easier to set time apart. But from time to time also makes it less interesting to work on these things as more relevant things are happening in the world
CC: @FredyC @urugator @spion @Bnaya @xaviergonz