-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Migrating from mobx 3 to mobx 4
The migration guide is pretty long and big. Sorry for that :-). The good news: most of the things you have probably never heard of. But the list should be complete. MobX 4 will detect most incorrect / deprecated api usages at runtime, so the easiest approach is to just upgrade, and use this list for trouble shooting. If you have TypeScript (or Flow) enabled in your project, most deprecated api's will already flagged at compile time.
MobX 4 dropped all api's that were already deprecated in MobX 3. So make sure you don't have any deprecation warnings before upgrading :)
The extras
namespace is removed, and all methods are now exposed at top level in the package. This allows for better tree shaking by tools like rollup or webpack.
MobX 4 introduces separation between the production and non production build. The production build strips most typechecks, resulting in a faster and smaller build. Make sure to substitute process.env.NODE_ENV = "production"
in your build process! If you are using MobX in a react project, you most probably already have set this up. Otherwise, the idea is explained here.
The observable.*
api has been simplified and made more uniform. Also, decorators can now be used both with decorator syntax enabled and disabled. For 80 - 90% the api will remain the same as in MobX 3, but for edge cases you will have to change some stuff.
Most api's are simplified. Many api's took for example optionally a name
as first argument, and some addition flags as last arguments. Almost all api's have moved to required arguments up front, and then an object with optional flags. This makes the api simpler and easier to read.
For typescript users, tslib
should be set to es6
in the config (compilation target can still be ES5)
Note; many things that were removed were removed to make the api surface smaller. If you think some feature shouldn't have been removed, feel free to open an issue!
shareGlobalState
has been removed. There are enough ways to setup a project with shared MobX properly :).
Passing a context
(this
) argument to autorun
, reaction
, runInAction
etc. etc. is no longer supported. Use arrow functions instead.
isModifierDescriptor
, isStrictModeEnabled
, extras.spyReport
, extras.spyReportEnd
, extras.spyReportStart
, extras.isSpyEnabled
no longer exist.
whyRun
has been removed, use the neat trace
feature instead.
The default
export of mobx is no longer exposed. This means you can no longer do import mobx from "mobx"
. Instead, import things explicitly: import { observable } from "mobx"
. (Or use import * as mobx from "mobx"
and kill tree-shaking)
Many things have moved from the extras
namespace. If they are prefixed with _
they are still unofficial api (but used for testing for example). Unprefixed functions are now officially supported.
Old api | New api | |
---|---|---|
useStrict(boolean) |
configure({ enforceActions: boolean }) |
|
expr |
Now part of the mobx-utils package |
|
createTransformer |
Now part of the mobx-utils package |
|
map(values) |
observable.map(values) |
|
extras.allowStateChanges |
_allowStateChanges |
|
extras.deepEqual |
comparer.structural |
|
extras.getAdministration |
getAdministration |
|
extras.getAtom |
getAtom |
|
extras.getDebugName |
getDebugName |
|
extras.getDependencyTree |
getDependencyTree |
|
extras.getGlobalState |
_getGlobalState |
|
extras.getObserverTree |
getObserverTree |
|
extras.interceptReads |
_interceptReads |
|
extras.isComputingDerivation |
_isComputingDerivation |
|
extras.isolateGlobalState() |
configure({ isolateGlobalState: true }) |
|
extras.onReactionError |
onReactionError |
|
extras.reserveArrayBuffer(number) |
configure({ arrayBuffer: number }) |
|
extras.resetGlobalState |
_resetGlobalState |
|
extras.setReactionScheduler(fn) |
configure({ reactionScheduler : fn }) |
The decorators observable.ref
, observable.shallow
, observable.deep
, observable.struct
can no longer be used as functions, instead, we made the api more consistent by always using these decorators really as decorators.
For example, observable.object
and extendObservable
now support a decorators
parameter.
One should use the decorators parameter where in Mobx 3 decorators were used as object modifiers. For example:
// MobX 3
const myObject = observable({
name: "Michel",
profile: observable.ref(someDataFetchingPromise)
})
// MobX 4
const myObject = observable({
name: "Michel",
profile: someDataFetchingPromise
}, {
// specify the decorators. 'observable' is the default, so we only mention 'profile':
profile: observable.ref // n.b.; no ()!
})
The advantage is that the usage between @decorator
and decorator
is now consistent, and they can always be called in the same way and with the same arguments. For example, the following examples now achieve all the same, and you will notice it is much easier to switch between different syntaxes or ways of creating objects:
class Todo {
@observable title = "test"
@observable.ref promise = somePromise
@computed get difficulty() {
return this.title.length
}
@computed({ requiresReaction: true })
get expensive() {
return somethingExpensive()
}
@action setTitle(t) {
this.title = t
}
@action.bound setTitle2(t) {
this.title = t
}
}
// observable.object takes a second 'decorators' param, specifying which decorators need to be applied.
// defaulting to `observable` for omitted fields (or `computed` for getters)
const todo = observable.object({
title: "test",
promise: somePromise,
get difficulty() {
return this.title.length
},
get expensive() {
return somethingExpensive()
},
setTitle(t) {
this.title = t
},
setTitle2(t) {
this.title = t
}
}, {
// title: observable can be omitted, it is the default when using observable.object / extendobservable
ref: observable.ref,
expensive: computed({ requiresReaction: true }),
setTitle: action,
setTitle2: action.bound
})
// Maybe you have classes, but no decorator syntax enabled. Don't worry, with MobX 4 you don't have to fall back to
// `extendObservable` in the constructor! Instead, just declare the fields and use `mobx.decorate` to enhance the prototype:
class Todo {
title = "test"
promise = somePromise
get difficulty() {
return this.title.length
}
get expensive() {
return somethingExpensive()
}
setTitle(t) {
this.title = t
}
setTitle2(t) {
this.title = t
}
}
decorate(Todo, {
title: observable,
ref: observable.ref,
expensive: computed({ requiresReaction: true }),
setTitle: action,
setTitle2: action.bound
})
Old api | New api | Notes |
---|---|---|
observable(primitive value) |
observable.box(primitive value) |
As decorator @observable still works the same (also for primtive values). If the value is a plain object, array, or ES6 Map, observable(value) will keep working as is |
observable(class instance) |
observable.box(class instance) |
As decorator @observable still works the same (also for primitive values). If the value is a plain object, array, or ES6 Map, observable(value) will keep working as is |
observable.shallowArray(values) |
observable.array(values, { deep: false }) |
|
observable.shallowMap(values) |
observable.map(values, { deep: false }) |
|
observable.shallowObject(values) |
observable.object(values, {}, { deep: false }) |
Note the empty object as second argument, which can contain decorators (see above) |
extendObservable(target, props, moreProps, evenMoreProps) |
extendObservable(target, {...props, ...moreProps, ...evenMoreProps}) |
extendObservable no longer accepts multiple bags of properties. Rather, merge then first, then extend the target. |
extendShallowObservable(target, props) |
extendObservable(target, props, {}, { deep: false }) |
Note the empty object as third argument, which can contain decorators (see the release notes) |
@computed.equals(compareFn) |
@computed({ equals: compareFn }) |
|
autorunAsync(fn, delay) |
autorun(fn, { delay: delay }) |
|
autorun(name, fn) |
autorun(fn, { name: name }) |
|
when(name, predicate, effect) |
when(predicate, effect, { name: name }) |
|
reaction(name, expr, effect) |
reaction(expr, effect, { name: name }) |
|
isComputed(thing, prop) |
isComputedProp(thing, prop) |
isComputed(thing) still works as is |
isObservable(thing, prop) |
isObservableProp(thing, prop) |
isObservable(thing) still works as is |
new Atom(name, fn, fn) |
createAtom(name, fn, fn) |
The Atom class is no longer exposed from the package directly. Note that there are now global functions onBecomeObserved and onBecomeUnobserved than can be used on any MobX observable, so in many cases you might not need custom atoms at all. |
computed(fn, { struct: true }) / computed(fn, { compareStructural: true })
|
computed(fn, { equals: comparer.structural }) |
MobX now requires Map
to be globally available. Make sure to polyfill them in older browser versions. (In practice, IE 10 and older). When using observable.map()
and serializing, you may need to update your code. observableMap.toJS()
now returns a shallow instance of Map
. To keep the behavior of creating a plain JavaScript object, use observableMap.toJSON()
extendObservable
can no longer redeclare existing properties. It can only introduce new properties. Use set
instead to introduce new properties or update existing ones
All iterables no longer create an array as iterator, but only a real iterator, to be more closely aligned to the official specs. So previously observableMap.values()
would return an array and iterator, but now it will only return an iterator. So you can no longer do observableMap.values().map(fn)
. Instead, use Array.from(observableMap.values()).map(fn)
or mobx.values(observableMap).map(fn)
. The affected iterators are: 1. The default iterator for observable arrays. 2. The default iterator for observable maps. observableMap.entries()
, observableMap.keys()
and observableMap.values()
.
The data passed to spy
handlers have been changed slightly.
Calling reportObserved()
on a self made atom will no longer trigger the hooks if reportObserved
is triggered outside a reactive context.