Proxy 支持
默认情况下,MobX 使用 Proxy 代理方式来让数组以及纯对象可观察。Proxy 方式能够提供最佳的性能表现以及在不同环境下大多数行为的一致性。 但是如过你的目标环境不支持 Proxy,你也可以通过配置将 Proxy 支持关闭。 这种情况大部分是由于你需要支持IE或 没有使用Hermes引擎的 React Native环境。
使用configure
方法来关闭Proxy支持
import { configure } from "mobx"
configure({
useProxies: "never"
})
useProxies
属性可被设置的值如下:
"always"
(默认值): MobX 只能运行在支持Proxy
的环境中,如果环境不支持 Proxy 将报错。"never"
: Proxy支持将不会被使用,MobX降级到non-proxy
替代方案。 兼容 ES5 环境, 但是会带来一些限制 limitations."ifavailable"
(实验阶段): 如果环境支持则启用 Proxy,否则 降级到non-proxy
替代方案。这个模式的优势是:MobX将对不能在ES5环境中使用的API以及语言特性发出警告,触发ES5标准限制时抛出错误。
注意: 在MobX 6 之前,你需要面临 MobX 4(兼容历史老旧引擎) 还是 MobX 5(支持现代引擎)的选择,然而现在MobX 6 将根据你的引擎环境引入特定API的polyfill
,(比如在只支持ES5标准的环境中使用map方法)。Proxy 不能被 polyfilled,虽然目前确实已经有 这样的 polyfill垫片了,但是他们并不能支持所有的场景,因此也不适用于 MobX,不要使用他们。
关闭 Proxy 支持情况下的使用限制
1.可观察的数组不再是真正的数组,因此使用 Array.isArray
方法时将会返回 false
。实际场景中,你在传递数组给其他模块时需要先使用.slice()
操作来为原始数组创建一份浅拷贝,举个例子,concat
操作在可观察数组上时将不会生效,因此你需要先使用.slice()
。
2.在创建可观察的纯对象之后,对其进行添加/删除的属性操作自动观察将不会生效。如果你想将通过index类数组下标的方式使用对象或者其他动态的集合明请使用可观察的Maps来替代。
在不支持Proxy情况下,也是有方法使这些(add/delete)动态操作观察生效的。那就是通过Collection utilities {🚀}工具集。你需要确保1.新属性的添加是通过工具集的set
方法 2.使用工具集的 values
/keys
/entries
来迭代对象 而不是 JavaScript内置方法。
但是由于这经常会被忘记,所以我们还是推荐尽量使用 可观察的Maps来替代。
Decorator support
For enabling experimental decorator support check out the Enabling decorators {🚀} section.
Linting options
To help you adopt the patterns advocated by MobX, a strict separation between actions, state and derivations, MobX can "lint" your coding patterns at runtime by hinting at smells. To make sure MobX is as strict as possible, adopt the following settings and read on for their explanations:
import { configure } from "mobx"
configure({
enforceActions: "always",
computedRequiresReaction: true,
reactionRequiresObservable: true,
observableRequiresReaction: true,
disableErrorBoundaries: true
})
At some point you will discover that this level of strictness can be pretty annoying. It is fine to disable these rules to gain productivity once you are sure you (and your colleagues) grokked the mental model of MobX.
Also, occassionally you will have a case where you have to supress the warnings triggered by these rules (for example by wrapping in runInAction
).
That is fine, there are good exceptions to these recommendations.
Don't be fundamentalistic about them.
enforceActions
The goal of enforceActions is that you don't forget to wrap event handlers in action
.
Possible options:
"observed"
(default): All state that is observed somewhere needs to be changed through actions. This is the default, and the recommended strictness mode in non-trivial applications."never"
: State can be changed from anywhere."always"
: State always needs to be changed through actions, which in practice also includes creation.
The benefit of "observed"
is that it allows you to create observables outside of actions and modify them freely, as long as they aren't used anywhere yet.
Since state should in principle always be created from some event handlers, and event handlers should be wrapped, "always"
captures this the best. But you probably don't want to use this mode in unit tests.
In the rare case where you create observables lazily, for example in a computed property, you can wrap the creation ad-hoc in an action using runInAction
.
computedRequiresReaction: boolean
Forbids the direct access of any unobserved computed value from outside an action or reaction.
This guarantees you aren't using computed values in a way where MobX won't cache them. Default: false
.
In the following example, MobX won't cache the computed value in the first code block, but will cache the result in the second and third block:
class Clock {
seconds = 0
get milliseconds() {
console.log("computing")
return this.seconds * 1000
}
constructor() {
makeAutoObservable(this)
}
}
const clock = new Clock()
{
// This would compute twice, but is warned against by this flag.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
}
{
runInAction(() => {
// Will compute only once.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
})
}
{
autorun(() => {
// Will compute only once.
console.log(clock.milliseconds)
console.log(clock.milliseconds)
})
}
observableRequiresReaction: boolean
Warns about any unobserved observable access.
Use this if you want to check whether you are using observables without a "MobX context".
This is a great way to find any missing observer
wrappers, for example in React components. But it will find missing actions as well. Default: false
configure({ observableRequiresReaction: true })
Note: using propTypes on components that are wrapped with observer
might trigger false positives for this rule.
reactionRequiresObservable: boolean
Warns when a reaction (e.g. autorun
) is created without accessing any observables.
Use this to check whether you are unnecessarily wrapping React components with observer
, wrapping functions with action
, or find cases where you simply forgot to make some data structures or properties observable. Default: false
configure({ reactionRequiresObservable: true })
disableErrorBoundaries: boolean
By default, MobX will catch and re-throw exceptions happening in your code to make sure that a reaction in one exception does not prevent the scheduled execution of other, possibly unrelated, reactions. This means exceptions are not propagated back to the original causing code and therefore you won't be able to catch them using try/catch.
By disabling error boundaries, exceptions can escape derivations. This might ease debugging, but might leave MobX and by extension your application in an unrecoverable broken state. Default: false
.
This option is great for unit tests, but remember to call _resetGlobalState
after each test, for example by using afterEach
in jest, for example:
import { _resetGlobalState, observable, autorun, configure } from "mobx"
configure({ disableErrorBoundaries: true })
test("Throw if age is negative", () => {
expect(() => {
const age = observable.box(10)
autorun(() => {
if (age.get() < 0) throw new Error("Age should not be negative")
})
age.set(-1)
}).toThrow("Age should not be negative")
})
afterEach(() => {
_resetGlobalState()
})
safeDescriptors: boolean
MobX makes some fields non-configurable or non-writable to prevent you from doing things that are not supported or would most likely break your code. However this can also prevent spying/mocking/stubbing in your tests.
configure({ safeDescriptors: false })
disables this safety measure, making everything configurable and writable.
Note it doesn't affect existing observables, only the ones created after it's been configured.
Use with caution and only when needed - do not turn this off globally for all tests, otherwise you risk false positives (passing tests with broken code). Default: true
configure({ safeDescriptors: false })
Further configuration options
isolateGlobalState: boolean
Isolates the global state of MobX when there are multiple instances of MobX active in the same environment. This is useful when you have an encapsulated library that is using MobX, living in the same page as the app that is using MobX. The reactivity inside the library will remain self-contained when you call configure({ isolateGlobalState: true })
from it.
Without this option, if multiple MobX instances are active, their internal state will be shared. The benefit is that observables from both instances work together, the downside is that the MobX versions have to match. Default: false
configure({ isolateGlobalState: true })
reactionScheduler: (f: () => void) => void
Sets a new function that executes all MobX reactions.
By default reactionScheduler
just runs the f
reaction without any other behavior.
This can be useful for basic debugging, or slowing down reactions to visualize application updates. Default: f => f()
configure({
reactionScheduler: (f): void => {
console.log("Running an event after a delay:", f)
setTimeout(f, 100)
}
})