mobxjs / mobx

Simple, scalable state management.
http://mobx.js.org
MIT License
27.55k stars 1.78k forks source link

Road to 4.0 #1076

Closed mweststrate closed 6 years ago

mweststrate commented 7 years ago

Hi all!

Time to start thinking about the next major of MobX. These are some of the ideas I have around them. Global goal:

Leverage modern features in evergreen browsers to address current quirks in the MobX api

Quirks being:

  1. Observable arrays not being arrays,
  2. property additions not being observed
  3. Maps only supporting primitive keys and Sets not being present #800

Tools to address the quirks: levering proxies #776 and native map implementations #800

Breaking changes

Also fix #940 if not done before

Tasks:

stevefan1999-personal commented 7 years ago

long live the MobX

hopefully it could defeat Redux in one day

andykog commented 7 years ago

bq. Proxy arrays, kill faux array @mweststrate, do you really think of dropping IE support? :-( My granny wont be able to visit my website :-(

mweststrate commented 7 years ago

@andykog I think 3.0 should be maintained still, but want to prepare for the future as well. Promised for a long time now that certain issues will be fixed when proxies are available, and their support is getting more decent now :)

marvinhagemeister commented 7 years ago

@mweststrate Although we do have some clients who use IE11 explicitly, I do very much support going all in with Proxies for mobx. I'm really excited about them and think they will make the core a lot simpler.

andykog commented 7 years ago

Not everybody is so lucky that they can ignore IE users. You can't tell managers that we have to drop about 4% of customers because that will make mobx core simpler. This puts many user in a position where they have to decide wheather they gonna stick to an older mobx version or use something different for their apps. My opinion is that this is to early for switching to proxies.

Keats commented 7 years ago

Compatibilty of:

I don't think I would be able to switch immediately to v4 but I would support using proxies and Map anyway, it's not like 3.0 is going to stop working.

mattruby commented 7 years ago

About 5% of our traffic is still stuck on IE. Regardless, I think it's a really cool idea to start exploring the new proxy features. I think we should be super duper explicit about what v4 is all about. We may want to call it mobx-next or something. I do worry that if we wanted to make significant changes to 3.x we'd have to jump past v4. That could be a little funky.

On Thu, Jul 6, 2017 at 2:56 AM, Vincent Prouillet notifications@github.com wrote:

Compatibilty of:

I don't think I would be able to switch immediately to v4 but I would support using proxies and Map anyway, it's not like 3.0 is going to stop working.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/1076#issuecomment-313324108, or mute the thread https://github.com/notifications/unsubscribe-auth/AAIrch_yNRH0QHVYvPv7SFJWuSX2Mfz_ks5sLJMdgaJpZM4ONAsS .

-- -Matt Ruby- mattruby@gmail.com

andykog commented 7 years ago

+1 for mobx-next, thought about that as well

jamiewinder commented 7 years ago

I could be wrong, but I think

  1. Maps only supporting primitive keys and Sets not being present

can be implemented now without any loss of browser support (with a polyfilled ES6 Map)? If so, it might be worth this - and any other breaking additions which needn't affect browser support - being the basis of v4, and v5 (or mobx-next) forming the basis for the big move to using Proxy?

stevefan1999-personal commented 7 years ago

https://github.com/ascoders/dynamic-object

here's an object observer with proxy that features similar functionality like mobx, using almost purely Proxy. Indeed the project is written by a Chinese, hence I doubt your ability to read the documents and comments, but you guys can take it as a code reference.

Keats commented 7 years ago

I would be against mobx-next that sounds like it is a completely new API etc. I don't really see the issue with using v4 for breaking changes, there are polyfills for Map and maybe for proxy (https://github.com/GoogleChrome/proxy-polyfill). v3 will still work and would likely be in maintenance mode

urugator commented 7 years ago

Some quick notes:

mweststrate commented 7 years ago

@urugator tnx!

Also: Upgrade to latest TS

Strate commented 7 years ago

Can we avoid "dispose" somehow, for example storing listeners to WeakMap with observable as a key?

pseudocode:

const map = new WeakMap()
observe(obj, listener) {
  map.set(obj, listener)
}
acronoah commented 7 years ago

Managing reactions/autorun disposal is my one difficulty with Mobx. Especially when the autorun is on something other than a component (and thus without an unmount/dispose...)

iamdanthedev commented 7 years ago

Having struggled recently with async computed functionality, I woukd propose it out of the box.

andrew-luminal commented 7 years ago

@rasdaniil +1

Better, clearer support for async, both for computed and for action, would be high on my list. I don't know how feasible it is to improve, but runInAction feels very kludgy to have to use after every await in an async action.

mweststrate commented 7 years ago

Check the recently added asyncAction in mobx-utils package! Removes the clumsiness

Op do 27 jul. 2017 17:21 schreef Andrew Metcalf notifications@github.com:

@rasdaniil https://github.com/rasdaniil +1

Better, clearer support for async, both for computed and for action, would be high on my list. I don't know how feasible it is to improve, but runInAction feels very kludgy to have to use after every await in an async action.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/1076#issuecomment-318394878, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhFo-5r6a8qx8B2X2gRd4TUazpR5Nks5sSKsAgaJpZM4ONAsS .

tkrotoff commented 7 years ago

@andykog

You can't tell managers that we have to drop about 4% of customers

MobX releases:

By the time MobX v4 is released, IE usage will be lower. + a few more months because you prefer a bit of dust on new things + the time it takes to switch to v4 (upgrade code, test, document, release) => IE will be even lower

Voilà

aliakakis commented 7 years ago

@stevefan1999 Redux is OK but still, I do get quite intrigued when I listen as an argument that Redux is for large apps and MobX for small/medium. Not to mention that after a while the amount of files and complexity a Redux based project has to deal with is absurd.

@mweststrate 👍 for killing faux arrays

OshotOkill commented 7 years ago

Good time to looking for the future. I approve the plan of switching to proxies underneath. Besides, React 16 requires Map & Set in runtime environment and it's mandatory.

jamiewinder commented 7 years ago

@OshotOkill - But Map and Set can be polyfilled. Proxy cannot.

phiresky commented 7 years ago

@jamiewinder But wouldn't the existing partial polyfill for Proxy cause a rewritten version of mobx to behave just like the current version does (i.e. new properties are not tracked)?

Then mobx4 could just have a compatibility flag that disallows adding new properties to objects and browser compatibility would stay the same.

jamiewinder commented 7 years ago

@phiresky I'm not really sure what the extent of the limitations of that polyfill are apart from the ones you highlighted. One that does start out is the faux array issue of MobX - observable arrays are not arrays. If MobX were to rely on a proxy polyfill, then we'd be in an ever more precarious position where an observable array may or may not 'be' an array depending on whether you're using the polyfill.

I think the switch to proxies should be absolute. i.e. if you need to support non-proxy browsers, then I'm afraid you'll have to stick to the previous version. However, I'm very likely one of those people who'll have to do this, and I'm missing some of the other features that are hoping to be added such as non-string keyed observable maps which can be added but will be a breaking change.

It'd be nice (though I wouldn't presume to ask for it) to see a MobX 4 that adds these features (and require polyfilled Map, Set), and a MobX 5 that forces you to use Proxy, but this might lead to confusion and having to maintain two active versions. So I'm not sure.

mweststrate commented 7 years ago

Also interesting; when will the new babel decorator implementation be ready? Should 4.0 be based on the old or new implementation (and semantics).

Op ma 4 sep. 2017 om 10:44 schreef jamiewinder notifications@github.com:

@phiresky https://github.com/phiresky I'm not really sure what the limitations of that polyfill apart from the ones you highlighted. One that does start out is the faux array issue of MobX - observable arrays are not arrays. If MobX were to rely on a proxy polyfill, then we'd be in an ever more precarious position where an observable array may or may not 'be' an array depending on whether you're using the polyfill.

I think the switch to proxies should be absolute. i.e. if you need to support non-proxy browsers, then I'm afraid you'll have to stick to the previous version. However, I'm very likely one of those people who'll have to do this, and I'm missing some of the other features that are hoping to be added such as non-string keyed observable maps which can be added but will be a breaking change.

It'd be nice (though I wouldn't presume to ask for it) to see a MobX 4 that adds these features (and polyfill Map, Set), and a MobX 5 that forces you to use Proxy, but this might lead to confusion and having to maintain two active versions. So I'm not sure.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/1076#issuecomment-326903242, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhLyuvLuVPmNxWFW0_n_1YCJv5Bg-ks5se7h7gaJpZM4ONAsS .

mweststrate commented 7 years ago

For implementation details see https://github.com/ascoders/dob, #776

solkimicreb commented 7 years ago

Just a quick note, because I see people mentioning only IE here. React Native is still not supporting Proxies, which is more important then IE in my opinion (other platforms are fine). Does anyone know when they plan to start supporting them?

farwayer commented 7 years ago

@solkimicreb As I know Proxy supported in iOS starting from 10.0. I didn't tested yet but it should be available on iOS because React Native uses system JSCore. On Android Proxies is not supported yet.

urugator commented 7 years ago

Make use of valueOf in comparer.structural see #1118

solkimicreb commented 7 years ago

About Proxies on React Native. I am pretty sure they are still not supported: https://facebook.github.io/react-native/docs/javascript-environment.html

caesarsol commented 7 years ago

@mweststrate Hi, what about using Proxy only in development, to check for uncorrectly used keys or unexistant ones?

That way would improve developer experience and detecting common errors, but without dropping support for old browsers!

caesarsol commented 7 years ago

About Maps: I actually liked how users are forced to use Map with dynamic keys, that sounds lile a good practice in general JS!

ascoders commented 7 years ago

I tried proxy-polyfill, but there are some unstable problems in the ie environment, and can not support deleteProperty, resulting in missing functionality.

So if 4.0 is no longer compatible ie it may be a reliable decision.

@mweststrate thanks for mentioning the library I wrote, If you can support the decorator, it may be easier to understand and more convenient to write:

@observable
class MyStore {
  name = 'ascoders' // Automatic observable
  articles = [] // Automatic observable
}
farwayer commented 7 years ago

@solkimicreb I tested Proxy in React Native on iOS and it works. Big disadvantage Proxy is not supported in React Native on Android and there is no plans to support it in the near future.

UPDATED: I found info JSC may be upgraded for Android in React Native in few months. But there is no real ETA though. In any case we can recommend to use custom JSC in readme if this does not happen before the 4.0 release.

Hi, what about using Proxy only in development,

@caesarsol IMO it is bad idea because something can work in dev but don't work in prod and vice versa. I prefer keep dev and prod environments as close as possible to prevent some strange errors. This is very actual for JS with tons of polyfils and translators.

Strate commented 7 years ago

The idea to use Proxy for dev environments to detect access to non-existing keys of objects and warn developer about it looks good for me! It will conver mos frequent developer error with mobx.

caesarsol commented 7 years ago

@farwayer that would be only for warning the developer, not to add functionality! So the behavior wouldn't be forked :)

@Strate thanks!

solkimicreb commented 7 years ago

@farwayer Thanks for the info! That's pretty good news 🙂

mweststrate commented 7 years ago

Other proposed changes:

mweststrate commented 7 years ago

Improved plan:

FIrst split mobx into mobx and mobx-core packages. So that the future and current api are powered by the same package. Would also be great for people who hate the current mobx api with faux arrays, side-effectful getters etc etc. They still could leverage the algorithms behind mobx and build their own library on top of atoms etc.

tkrotoff commented 7 years ago

FYI Vue.js 3.0 (or Vue.js 2.6-next) will use ES6 Proxy: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf

urugator commented 7 years ago

FIrst split mobx into mobx and mobx-core packages

I wanted to suggest something like that, but then I wasn't sure what exactly can be reused and whether some adjustments won't be required. So I thought it may be better to came up with proxy implementation first and then try to identify the common parts and split the package.

ippa commented 7 years ago

@farwayer @solkimicreb @mweststrate

i haven't tried it but the latest release of Expo ("pre-compiled react-native") claims to come with an updated Android JavaScriptCore including among others proxy-support ( https://blog.expo.io/expo-sdk-v22-0-0-is-now-available-7745bfe97fc6 ).

Updated the Android version of JSC to be closer to iOS 10.3’s JSC. Features like WeakMap can be used natively on Android and iOS. On Android and iOS 10+, more features like Proxies are supported.

If I've understood it correct, native proxy support will fix all of mobx quirks when it comes to observing and reacting to more complex data?

capaj commented 6 years ago

@strate yeah detecting an extra property on an observable should be a feature added to the current semver version of MobX. It would catch quite a few developer errors. Although there might be people who do this on purpose. Maybe do the check only in strict mode?

mweststrate commented 6 years ago

Proposal for a cleaned up observable API:


--- Current

@observable
@observable.ref
@observable.deep
@observable.shallow

observable.box 
observable.shallowBox
observable.object
observable.shallowObject
observable.array
observable.shallowArray
observable.map
observable.shallowMap

extendObservable
extendShallowObservable

@computed
@computed.struct
@computed.equals
@action
@action.bound

--- New

@ref // just stores an observable reference
@collection // make any collection it receives observable (1 level deep), objects, arrays, Maps, Sets. Also accepts `null` and `undefined`
@deepObservable // like current `observable`. makes anything deeply observable

ref(value, opts) // creates a boxed observable, with .get / .set
collection(value, opts) // creates an observable wrapper around collection (does not accept `null` / `undefined`)

extend(thing, props, opts) // like extendShallowObservable / extendObservable

@computed
@computed.struct
@computed.equals
@action
@action.bound

type opts = {
   deep? = false,
   name?: string
   // dunno what more...
}
jamiewinder commented 6 years ago

I can't say I'm keen on the renames. I was a big fan of this API change in MobX 3 as it described they describe the intent perfectly.

@ref implies reference, but nothing about its observable nature. Likewise for @collection. Having these things hang off of observable makes it obvious.

stevefan1999-personal commented 6 years ago

We might need mobx-compat after the observable => ref refactor

phiresky commented 6 years ago

I have to say I also like having all the different kinds of observable scoped in observable. Also your list is missing observable.struct?

In fact, why is computed not also scoped in observable? observable.computed would make sense :)

The thing I think would make sense to change is to prevent usage of observable() for both primitives and objects, because it's weird that observable({a: 1}) returns something that looks like {a: 1} but observable(1) creates an object with {get: () => 1, set: val => void}, I'd just forbid that.

It's also pretty confusing when starting out that observable(classInstance) creates an observable.ref instead of a deep observable.

Also I think all isMobxModifierDescriptor things work kind of confusing: observable.ref() is a completely different kind of thing than observable.box (you must ref() within another observable, and the opposite for box), but only when not used as a decorator.

phiresky commented 6 years ago

I also think shallow is a far clearer name than collection. Renaming observable to deepObservable might be a good idea, not sure.

Here's how I would change the API:


observable(x) 
    -> deeply observe all properties of x. if x is a primitive, 
       null or a class instance then throw an error
@observable x = ... 
    -> deeply as above, but also allow what's basically a ref (primitives, null, class instances, etc.)

observable.shallow
   -> error, modifier are a separate API. Before, I would think
      observable.shallow({a: {}, b: {}}) would give me an object with
      observable references to a and b, but it doesn't.
@observable.shallow -> as before

observable.ref -> error, modifiers are separate API
@observable.ref -> as before

@observable.computed
get x() { ...}    -> as before

observable.computed(() => ...)  -> error, docs even say "This form of computed
                                   is not used very often"
observable.computed.boxed(() => ...)  -> `{get(), ...}` as computed() was before

// *very clearly* only do this kind of thing (completely modify the interface
   of "x") in the boxed method
boxedObservable(x) -> {get() => x, set(x) => void}
@boxedObservable -> error, no one needs this
// I also think it would be better to have this not be deep observable
// automatically, but I'm not sure.

* Modifiers

modifier.ref() -> as observable.ref() before
modifier.shallow() -> as observable.shallow before, but throw an error if any
                      primitives or null are passed. what does
                      observable.shallow("123") mean??
...
urugator commented 6 years ago

To me the proposal seems:

Counter proposal: What about depth option (probably already discussed)? Depth seems quite self explanatory (?) and universally usable, so we could get rid of ref/shallow/deep/box: depth: -1 (default) is deep depth: 0 is for ref or box
depth: 1 is for shallow depth: n shouldn't be too hard to support any arbitrary depth (?) alternatively we can treat n>1 as deep

The question is how to specify "depth" (or any option in general), so I would go for:

@observable() // brackets are mandatory
@observable({ depth: 1 })
@computed()
@computed({ equals: () => {} })

observable(o, { depth: 1, name: "name" })

/*
Dunno about extend... 
I am convinced that options object should be defined per property same as decorators.
However defining key + value + options in consistent way, without being too verbose is quite a challenge.  
So aside of current and a bit magical "descriptor/modifier" solution, there are some less magical, but more verbose alternatives:  
*/

// Object.defineProperty approach
defineProp(o, key, {
  value: val,
  depth: 1,  
})
defineProps(o, {
  key: {
    value: val,
    depth: 1
  }
})

// Helper for applying decorators
decorate(o, {
  key1: observable(), // value is taken from "o.key1" 
  key2: observable({ depth: 1 }), // value is taken from "o.key2"    
})
// we could allow "value/initialValue" option

EDIT: btw I also don't have much of a problem with current API

spion commented 6 years ago

Whats wrong with the current API? Its pretty good.

My paint of the bikeshed:

@observable.ref // only the reference
@observable.collection // converts refs + one level items
@observable // automatically converts assignments to deep observable
observable.(map|set|array|etc) // "shallow" collections (don't auto-convert contents)
observable(collection) // automatically converts objects/collection/subcollections, deeply

additionally, any existing tagged observables would stop deep conversion, since the consumed object knows better what should and shouldn't be (deeply) observable.