mobx
Version:
Simple, scalable state management.
1,274 lines (1,253 loc) • 174 kB
JavaScript
/** MobX - (c) Michel Weststrate 2015 - 2019 - MIT Licensed */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __values(o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
var OBFUSCATED_ERROR = "An invariant failed, however the error is obfuscated because this is an production build.";
var EMPTY_ARRAY = [];
Object.freeze(EMPTY_ARRAY);
var EMPTY_OBJECT = {};
Object.freeze(EMPTY_OBJECT);
function getNextId() {
return ++globalState.mobxGuid;
}
function fail(message) {
invariant(false, message);
throw "X"; // unreachable
}
function invariant(check, message) {
if (!check)
throw new Error("[mobx] " + (message || OBFUSCATED_ERROR));
}
/**
* Prints a deprecation message, but only one time.
* Returns false if the deprecated message was already printed before
*/
var deprecatedMessages = [];
function deprecated(msg, thing) {
if (process.env.NODE_ENV === "production")
return false;
if (thing) {
return deprecated("'" + msg + "', use '" + thing + "' instead.");
}
if (deprecatedMessages.indexOf(msg) !== -1)
return false;
deprecatedMessages.push(msg);
console.error("[mobx] Deprecated: " + msg);
return true;
}
/**
* Makes sure that the provided function is invoked at most once.
*/
function once(func) {
var invoked = false;
return function () {
if (invoked)
return;
invoked = true;
return func.apply(this, arguments);
};
}
var noop = function () { };
function unique(list) {
var res = [];
list.forEach(function (item) {
if (res.indexOf(item) === -1)
res.push(item);
});
return res;
}
function isObject(value) {
return value !== null && typeof value === "object";
}
function isPlainObject(value) {
if (value === null || typeof value !== "object")
return false;
var proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
function addHiddenProp(object, propName, value) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: true,
configurable: true,
value: value
});
}
function addHiddenFinalProp(object, propName, value) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: false,
configurable: true,
value: value
});
}
function isPropertyConfigurable(object, prop) {
var descriptor = Object.getOwnPropertyDescriptor(object, prop);
return !descriptor || (descriptor.configurable !== false && descriptor.writable !== false);
}
function assertPropertyConfigurable(object, prop) {
if (process.env.NODE_ENV !== "production" && !isPropertyConfigurable(object, prop))
fail("Cannot make property '" + prop.toString() + "' observable, it is not configurable and writable in the target object");
}
function createInstanceofPredicate(name, clazz) {
var propName = "isMobX" + name;
clazz.prototype[propName] = true;
return function (x) {
return isObject(x) && x[propName] === true;
};
}
/**
* Returns whether the argument is an array, disregarding observability.
*/
function isArrayLike(x) {
return Array.isArray(x) || isObservableArray(x);
}
function isES6Map(thing) {
return thing instanceof Map;
}
function isES6Set(thing) {
return thing instanceof Set;
}
/**
* Returns the following: own keys, prototype keys & own symbol keys, if they are enumerable.
*/
function getPlainObjectKeys(object) {
var enumerables = new Set();
for (var key in object)
enumerables.add(key); // *all* enumerables
Object.getOwnPropertySymbols(object).forEach(function (k) {
if (Object.getOwnPropertyDescriptor(object, k).enumerable)
enumerables.add(k);
}); // *own* symbols
// Note: this implementation is missing enumerable, inherited, symbolic property names! That would however pretty expensive to add,
// as there is no efficient iterator that returns *all* properties
return Array.from(enumerables);
}
function stringifyKey(key) {
if (key && key.toString)
return key.toString();
else
return new String(key).toString();
}
function getMapLikeKeys(map) {
if (isPlainObject(map))
return Object.keys(map);
if (Array.isArray(map))
return map.map(function (_a) {
var _b = __read(_a, 1), key = _b[0];
return key;
});
if (isES6Map(map) || isObservableMap(map))
return Array.from(map.keys());
return fail("Cannot get keys from '" + map + "'");
}
function toPrimitive(value) {
return value === null ? null : typeof value === "object" ? "" + value : value;
}
var $mobx = Symbol("mobx administration");
var Atom = /** @class */ (function () {
/**
* Create a new atom. For debugging purposes it is recommended to give it a name.
* The onBecomeObserved and onBecomeUnobserved callbacks can be used for resource management.
*/
function Atom(name) {
if (name === void 0) { name = "Atom@" + getNextId(); }
this.name = name;
this.isPendingUnobservation = false; // for effective unobserving. BaseAtom has true, for extra optimization, so its onBecomeUnobserved never gets called, because it's not needed
this.isBeingObserved = false;
this.observers = new Set();
this.diffValue = 0;
this.lastAccessedBy = 0;
this.lowestObserverState = IDerivationState.NOT_TRACKING;
}
Atom.prototype.onBecomeObserved = function () {
if (this.onBecomeObservedListeners) {
this.onBecomeObservedListeners.forEach(function (listener) { return listener(); });
}
};
Atom.prototype.onBecomeUnobserved = function () {
if (this.onBecomeUnobservedListeners) {
this.onBecomeUnobservedListeners.forEach(function (listener) { return listener(); });
}
};
/**
* Invoke this method to notify mobx that your atom has been used somehow.
* Returns true if there is currently a reactive context.
*/
Atom.prototype.reportObserved = function () {
return reportObserved(this);
};
/**
* Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate.
*/
Atom.prototype.reportChanged = function () {
startBatch();
propagateChanged(this);
endBatch();
};
Atom.prototype.toString = function () {
return this.name;
};
return Atom;
}());
var isAtom = createInstanceofPredicate("Atom", Atom);
function createAtom(name, onBecomeObservedHandler, onBecomeUnobservedHandler) {
if (onBecomeObservedHandler === void 0) { onBecomeObservedHandler = noop; }
if (onBecomeUnobservedHandler === void 0) { onBecomeUnobservedHandler = noop; }
var atom = new Atom(name);
// default `noop` listener will not initialize the hook Set
if (onBecomeObservedHandler !== noop) {
onBecomeObserved(atom, onBecomeObservedHandler);
}
if (onBecomeUnobservedHandler !== noop) {
onBecomeUnobserved(atom, onBecomeUnobservedHandler);
}
return atom;
}
function identityComparer(a, b) {
return a === b;
}
function structuralComparer(a, b) {
return deepEqual(a, b);
}
function defaultComparer(a, b) {
return Object.is(a, b);
}
var comparer = {
identity: identityComparer,
structural: structuralComparer,
default: defaultComparer
};
var mobxDidRunLazyInitializersSymbol = Symbol("mobx did run lazy initializers");
var mobxPendingDecorators = Symbol("mobx pending decorators");
var enumerableDescriptorCache = {};
var nonEnumerableDescriptorCache = {};
function createPropertyInitializerDescriptor(prop, enumerable) {
var cache = enumerable ? enumerableDescriptorCache : nonEnumerableDescriptorCache;
return (cache[prop] ||
(cache[prop] = {
configurable: true,
enumerable: enumerable,
get: function () {
initializeInstance(this);
return this[prop];
},
set: function (value) {
initializeInstance(this);
this[prop] = value;
}
}));
}
function initializeInstance(target) {
if (target[mobxDidRunLazyInitializersSymbol] === true)
return;
var decorators = target[mobxPendingDecorators];
if (decorators) {
addHiddenProp(target, mobxDidRunLazyInitializersSymbol, true);
for (var key in decorators) {
var d = decorators[key];
d.propertyCreator(target, d.prop, d.descriptor, d.decoratorTarget, d.decoratorArguments);
}
}
}
function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) {
return function decoratorFactory() {
var decoratorArguments;
var decorator = function decorate(target, prop, descriptor, applyImmediately
// This is a special parameter to signal the direct application of a decorator, allow extendObservable to skip the entire type decoration part,
// as the instance to apply the decorator to equals the target
) {
if (applyImmediately === true) {
propertyCreator(target, prop, descriptor, target, decoratorArguments);
return null;
}
if (process.env.NODE_ENV !== "production" && !quacksLikeADecorator(arguments))
fail("This function is a decorator, but it wasn't invoked like a decorator");
if (!Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) {
var inheritedDecorators = target[mobxPendingDecorators];
addHiddenProp(target, mobxPendingDecorators, __assign({}, inheritedDecorators));
}
target[mobxPendingDecorators][prop] = {
prop: prop,
propertyCreator: propertyCreator,
descriptor: descriptor,
decoratorTarget: target,
decoratorArguments: decoratorArguments
};
return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable);
};
if (quacksLikeADecorator(arguments)) {
// @decorator
decoratorArguments = EMPTY_ARRAY;
return decorator.apply(null, arguments);
}
else {
// @decorator(args)
decoratorArguments = Array.prototype.slice.call(arguments);
return decorator;
}
};
}
function quacksLikeADecorator(args) {
return (((args.length === 2 || args.length === 3) && typeof args[1] === "string") ||
(args.length === 4 && args[3] === true));
}
function deepEnhancer(v, _, name) {
// it is an observable already, done
if (isObservable(v))
return v;
// something that can be converted and mutated?
if (Array.isArray(v))
return observable.array(v, { name: name });
if (isPlainObject(v))
return observable.object(v, undefined, { name: name });
if (isES6Map(v))
return observable.map(v, { name: name });
if (isES6Set(v))
return observable.set(v, { name: name });
return v;
}
function shallowEnhancer(v, _, name) {
if (v === undefined || v === null)
return v;
if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v) || isObservableSet(v))
return v;
if (Array.isArray(v))
return observable.array(v, { name: name, deep: false });
if (isPlainObject(v))
return observable.object(v, undefined, { name: name, deep: false });
if (isES6Map(v))
return observable.map(v, { name: name, deep: false });
if (isES6Set(v))
return observable.set(v, { name: name, deep: false });
return fail(process.env.NODE_ENV !== "production" &&
"The shallow modifier / decorator can only used in combination with arrays, objects, maps and sets");
}
function referenceEnhancer(newValue) {
// never turn into an observable
return newValue;
}
function refStructEnhancer(v, oldValue, name) {
if (process.env.NODE_ENV !== "production" && isObservable(v))
throw "observable.struct should not be used with observable values";
if (deepEqual(v, oldValue))
return oldValue;
return v;
}
function createDecoratorForEnhancer(enhancer) {
invariant(enhancer);
var decorator = createPropDecorator(true, function (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) {
if (process.env.NODE_ENV !== "production") {
invariant(!descriptor || !descriptor.get, "@observable cannot be used on getter (property \"" + stringifyKey(propertyName) + "\"), use @computed instead.");
}
var initialValue = descriptor
? descriptor.initializer
? descriptor.initializer.call(target)
: descriptor.value
: undefined;
asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
});
var res =
// Extra process checks, as this happens during module initialization
typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production"
? function observableDecorator() {
// This wrapper function is just to detect illegal decorator invocations, deprecate in a next version
// and simply return the created prop decorator
if (arguments.length < 2)
return fail("Incorrect decorator invocation. @observable decorator doesn't expect any arguments");
return decorator.apply(null, arguments);
}
: decorator;
res.enhancer = enhancer;
return res;
}
// Predefined bags of create observable options, to avoid allocating temporarily option objects
// in the majority of cases
var defaultCreateObservableOptions = {
deep: true,
name: undefined,
defaultDecorator: undefined,
proxy: true
};
Object.freeze(defaultCreateObservableOptions);
function assertValidOption(key) {
if (!/^(deep|name|equals|defaultDecorator|proxy)$/.test(key))
fail("invalid option for (extend)observable: " + key);
}
function asCreateObservableOptions(thing) {
if (thing === null || thing === undefined)
return defaultCreateObservableOptions;
if (typeof thing === "string")
return { name: thing, deep: true, proxy: true };
if (process.env.NODE_ENV !== "production") {
if (typeof thing !== "object")
return fail("expected options object");
Object.keys(thing).forEach(assertValidOption);
}
return thing;
}
var deepDecorator = createDecoratorForEnhancer(deepEnhancer);
var shallowDecorator = createDecoratorForEnhancer(shallowEnhancer);
var refDecorator = createDecoratorForEnhancer(referenceEnhancer);
var refStructDecorator = createDecoratorForEnhancer(refStructEnhancer);
function getEnhancerFromOptions(options) {
return options.defaultDecorator
? options.defaultDecorator.enhancer
: options.deep === false
? referenceEnhancer
: deepEnhancer;
}
/**
* Turns an object, array or function into a reactive structure.
* @param v the value which should become observable.
*/
function createObservable(v, arg2, arg3) {
// @observable someProp;
if (typeof arguments[1] === "string") {
return deepDecorator.apply(null, arguments);
}
// it is an observable already, done
if (isObservable(v))
return v;
// something that can be converted and mutated?
var res = isPlainObject(v)
? observable.object(v, arg2, arg3)
: Array.isArray(v)
? observable.array(v, arg2)
: isES6Map(v)
? observable.map(v, arg2)
: isES6Set(v)
? observable.set(v, arg2)
: v;
// this value could be converted to a new observable data structure, return it
if (res !== v)
return res;
// otherwise, just box it
fail(process.env.NODE_ENV !== "production" &&
"The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'");
}
var observableFactories = {
box: function (value, options) {
if (arguments.length > 2)
incorrectlyUsedAsDecorator("box");
var o = asCreateObservableOptions(options);
return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals);
},
array: function (initialValues, options) {
if (arguments.length > 2)
incorrectlyUsedAsDecorator("array");
var o = asCreateObservableOptions(options);
return createObservableArray(initialValues, getEnhancerFromOptions(o), o.name);
},
map: function (initialValues, options) {
if (arguments.length > 2)
incorrectlyUsedAsDecorator("map");
var o = asCreateObservableOptions(options);
return new ObservableMap(initialValues, getEnhancerFromOptions(o), o.name);
},
set: function (initialValues, options) {
if (arguments.length > 2)
incorrectlyUsedAsDecorator("set");
var o = asCreateObservableOptions(options);
return new ObservableSet(initialValues, getEnhancerFromOptions(o), o.name);
},
object: function (props, decorators, options) {
if (typeof arguments[1] === "string")
incorrectlyUsedAsDecorator("object");
var o = asCreateObservableOptions(options);
if (o.proxy === false) {
return extendObservable({}, props, decorators, o);
}
else {
var defaultDecorator = getDefaultDecoratorFromObjectOptions(o);
var base = extendObservable({}, undefined, undefined, o);
var proxy = createDynamicObservableObject(base);
extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
return proxy;
}
},
ref: refDecorator,
shallow: shallowDecorator,
deep: deepDecorator,
struct: refStructDecorator
};
var observable = createObservable;
// weird trick to keep our typings nicely with our funcs, and still extend the observable function
Object.keys(observableFactories).forEach(function (name) { return (observable[name] = observableFactories[name]); });
function incorrectlyUsedAsDecorator(methodName) {
fail(
// process.env.NODE_ENV !== "production" &&
"Expected one or two arguments to observable." + methodName + ". Did you accidentally try to use observable." + methodName + " as decorator?");
}
var computedDecorator = createPropDecorator(false, function (instance, propertyName, descriptor, decoratorTarget, decoratorArgs) {
var get = descriptor.get, set = descriptor.set; // initialValue is the descriptor for get / set props
// Optimization: faster on decorator target or instance? Assuming target
// Optimization: find out if declaring on instance isn't just faster. (also makes the property descriptor simpler). But, more memory usage..
// Forcing instance now, fixes hot reloadig issues on React Native:
var options = decoratorArgs[0] || {};
asObservableObject(instance).addComputedProp(instance, propertyName, __assign({ get: get,
set: set, context: instance }, options));
});
var computedStructDecorator = computedDecorator({ equals: comparer.structural });
/**
* Decorator for class properties: @computed get value() { return expr; }.
* For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`;
*/
var computed = function computed(arg1, arg2, arg3) {
if (typeof arg2 === "string") {
// @computed
return computedDecorator.apply(null, arguments);
}
if (arg1 !== null && typeof arg1 === "object" && arguments.length === 1) {
// @computed({ options })
return computedDecorator.apply(null, arguments);
}
// computed(expr, options?)
if (process.env.NODE_ENV !== "production") {
invariant(typeof arg1 === "function", "First argument to `computed` should be an expression.");
invariant(arguments.length < 3, "Computed takes one or two arguments if used as function");
}
var opts = typeof arg2 === "object" ? arg2 : {};
opts.get = arg1;
opts.set = typeof arg2 === "function" ? arg2 : opts.set;
opts.name = opts.name || arg1.name || ""; /* for generated name */
return new ComputedValue(opts);
};
computed.struct = computedStructDecorator;
function createAction(actionName, fn, ref) {
if (process.env.NODE_ENV !== "production") {
invariant(typeof fn === "function", "`action` can only be invoked on functions");
if (typeof actionName !== "string" || !actionName)
fail("actions should have valid names, got: '" + actionName + "'");
}
var res = function () {
return executeAction(actionName, fn, ref || this, arguments);
};
res.isMobxAction = true;
return res;
}
function executeAction(actionName, fn, scope, args) {
var runInfo = startAction(actionName, fn, scope, args);
var shouldSupressReactionError = true;
try {
var res = fn.apply(scope, args);
shouldSupressReactionError = false;
return res;
}
finally {
if (shouldSupressReactionError) {
globalState.suppressReactionErrors = shouldSupressReactionError;
endAction(runInfo);
globalState.suppressReactionErrors = false;
}
else {
endAction(runInfo);
}
}
}
function startAction(actionName, fn, scope, args) {
var notifySpy = isSpyEnabled() && !!actionName;
var startTime = 0;
if (notifySpy && process.env.NODE_ENV !== "production") {
startTime = Date.now();
var l = (args && args.length) || 0;
var flattendArgs = new Array(l);
if (l > 0)
for (var i = 0; i < l; i++)
flattendArgs[i] = args[i];
spyReportStart({
type: "action",
name: actionName,
object: scope,
arguments: flattendArgs
});
}
var prevDerivation = untrackedStart();
startBatch();
var prevAllowStateChanges = allowStateChangesStart(true);
return {
prevDerivation: prevDerivation,
prevAllowStateChanges: prevAllowStateChanges,
notifySpy: notifySpy,
startTime: startTime
};
}
function endAction(runInfo) {
allowStateChangesEnd(runInfo.prevAllowStateChanges);
endBatch();
untrackedEnd(runInfo.prevDerivation);
if (runInfo.notifySpy && process.env.NODE_ENV !== "production")
spyReportEnd({ time: Date.now() - runInfo.startTime });
}
function allowStateChanges(allowStateChanges, func) {
var prev = allowStateChangesStart(allowStateChanges);
var res;
try {
res = func();
}
finally {
allowStateChangesEnd(prev);
}
return res;
}
function allowStateChangesStart(allowStateChanges) {
var prev = globalState.allowStateChanges;
globalState.allowStateChanges = allowStateChanges;
return prev;
}
function allowStateChangesEnd(prev) {
globalState.allowStateChanges = prev;
}
function allowStateChangesInsideComputed(func) {
var prev = globalState.computationDepth;
globalState.computationDepth = 0;
var res;
try {
res = func();
}
finally {
globalState.computationDepth = prev;
}
return res;
}
var ObservableValue = /** @class */ (function (_super) {
__extends(ObservableValue, _super);
function ObservableValue(value, enhancer, name, notifySpy, equals) {
if (name === void 0) { name = "ObservableValue@" + getNextId(); }
if (notifySpy === void 0) { notifySpy = true; }
if (equals === void 0) { equals = comparer.default; }
var _this = _super.call(this, name) || this;
_this.enhancer = enhancer;
_this.name = name;
_this.equals = equals;
_this.hasUnreportedChange = false;
_this.value = enhancer(value, undefined, name);
if (notifySpy && isSpyEnabled() && process.env.NODE_ENV !== "production") {
// only notify spy if this is a stand-alone observable
spyReport({ type: "create", name: _this.name, newValue: "" + _this.value });
}
return _this;
}
ObservableValue.prototype.dehanceValue = function (value) {
if (this.dehancer !== undefined)
return this.dehancer(value);
return value;
};
ObservableValue.prototype.set = function (newValue) {
var oldValue = this.value;
newValue = this.prepareNewValue(newValue);
if (newValue !== globalState.UNCHANGED) {
var notifySpy = isSpyEnabled();
if (notifySpy && process.env.NODE_ENV !== "production") {
spyReportStart({
type: "update",
name: this.name,
newValue: newValue,
oldValue: oldValue
});
}
this.setNewValue(newValue);
if (notifySpy && process.env.NODE_ENV !== "production")
spyReportEnd();
}
};
ObservableValue.prototype.prepareNewValue = function (newValue) {
checkIfStateModificationsAreAllowed(this);
if (hasInterceptors(this)) {
var change = interceptChange(this, {
object: this,
type: "update",
newValue: newValue
});
if (!change)
return globalState.UNCHANGED;
newValue = change.newValue;
}
// apply modifier
newValue = this.enhancer(newValue, this.value, this.name);
return this.equals(this.value, newValue) ? globalState.UNCHANGED : newValue;
};
ObservableValue.prototype.setNewValue = function (newValue) {
var oldValue = this.value;
this.value = newValue;
this.reportChanged();
if (hasListeners(this)) {
notifyListeners(this, {
type: "update",
object: this,
newValue: newValue,
oldValue: oldValue
});
}
};
ObservableValue.prototype.get = function () {
this.reportObserved();
return this.dehanceValue(this.value);
};
ObservableValue.prototype.intercept = function (handler) {
return registerInterceptor(this, handler);
};
ObservableValue.prototype.observe = function (listener, fireImmediately) {
if (fireImmediately)
listener({
object: this,
type: "update",
newValue: this.value,
oldValue: undefined
});
return registerListener(this, listener);
};
ObservableValue.prototype.toJSON = function () {
return this.get();
};
ObservableValue.prototype.toString = function () {
return this.name + "[" + this.value + "]";
};
ObservableValue.prototype.valueOf = function () {
return toPrimitive(this.get());
};
ObservableValue.prototype[Symbol.toPrimitive] = function () {
return this.valueOf();
};
return ObservableValue;
}(Atom));
var isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue);
/**
* A node in the state dependency root that observes other nodes, and can be observed itself.
*
* ComputedValue will remember the result of the computation for the duration of the batch, or
* while being observed.
*
* During this time it will recompute only when one of its direct dependencies changed,
* but only when it is being accessed with `ComputedValue.get()`.
*
* Implementation description:
* 1. First time it's being accessed it will compute and remember result
* give back remembered result until 2. happens
* 2. First time any deep dependency change, propagate POSSIBLY_STALE to all observers, wait for 3.
* 3. When it's being accessed, recompute if any shallow dependency changed.
* if result changed: propagate STALE to all observers, that were POSSIBLY_STALE from the last step.
* go to step 2. either way
*
* If at any point it's outside batch and it isn't observed: reset everything and go to 1.
*/
var ComputedValue = /** @class */ (function () {
/**
* Create a new computed value based on a function expression.
*
* The `name` property is for debug purposes only.
*
* The `equals` property specifies the comparer function to use to determine if a newly produced
* value differs from the previous value. Two comparers are provided in the library; `defaultComparer`
* compares based on identity comparison (===), and `structualComparer` deeply compares the structure.
* Structural comparison can be convenient if you always produce a new aggregated object and
* don't want to notify observers if it is structurally the same.
* This is useful for working with vectors, mouse coordinates etc.
*/
function ComputedValue(options) {
this.dependenciesState = IDerivationState.NOT_TRACKING;
this.observing = []; // nodes we are looking at. Our value depends on these nodes
this.newObserving = null; // during tracking it's an array with new observed observers
this.isBeingObserved = false;
this.isPendingUnobservation = false;
this.observers = new Set();
this.diffValue = 0;
this.runId = 0;
this.lastAccessedBy = 0;
this.lowestObserverState = IDerivationState.UP_TO_DATE;
this.unboundDepsCount = 0;
this.__mapid = "#" + getNextId();
this.value = new CaughtException(null);
this.isComputing = false; // to check for cycles
this.isRunningSetter = false;
this.isTracing = TraceMode.NONE;
if (process.env.NODE_ENV !== "production" && !options.get)
throw "[mobx] missing option for computed: get";
this.derivation = options.get;
this.name = options.name || "ComputedValue@" + getNextId();
if (options.set)
this.setter = createAction(this.name + "-setter", options.set);
this.equals =
options.equals ||
(options.compareStructural || options.struct
? comparer.structural
: comparer.default);
this.scope = options.context;
this.requiresReaction = !!options.requiresReaction;
this.keepAlive = !!options.keepAlive;
}
ComputedValue.prototype.onBecomeStale = function () {
propagateMaybeChanged(this);
};
ComputedValue.prototype.onBecomeObserved = function () {
if (this.onBecomeObservedListeners) {
this.onBecomeObservedListeners.forEach(function (listener) { return listener(); });
}
};
ComputedValue.prototype.onBecomeUnobserved = function () {
if (this.onBecomeUnobservedListeners) {
this.onBecomeUnobservedListeners.forEach(function (listener) { return listener(); });
}
};
/**
* Returns the current value of this computed value.
* Will evaluate its computation first if needed.
*/
ComputedValue.prototype.get = function () {
if (this.isComputing)
fail("Cycle detected in computation " + this.name + ": " + this.derivation);
if (globalState.inBatch === 0 && this.observers.size === 0 && !this.keepAlive) {
if (shouldCompute(this)) {
this.warnAboutUntrackedRead();
startBatch(); // See perf test 'computed memoization'
this.value = this.computeValue(false);
endBatch();
}
}
else {
reportObserved(this);
if (shouldCompute(this))
if (this.trackAndCompute())
propagateChangeConfirmed(this);
}
var result = this.value;
if (isCaughtException(result))
throw result.cause;
return result;
};
ComputedValue.prototype.peek = function () {
var res = this.computeValue(false);
if (isCaughtException(res))
throw res.cause;
return res;
};
ComputedValue.prototype.set = function (value) {
if (this.setter) {
invariant(!this.isRunningSetter, "The setter of computed value '" + this.name + "' is trying to update itself. Did you intend to update an _observable_ value, instead of the computed property?");
this.isRunningSetter = true;
try {
this.setter.call(this.scope, value);
}
finally {
this.isRunningSetter = false;
}
}
else
invariant(false, process.env.NODE_ENV !== "production" &&
"[ComputedValue '" + this.name + "'] It is not possible to assign a new value to a computed value.");
};
ComputedValue.prototype.trackAndCompute = function () {
if (isSpyEnabled() && process.env.NODE_ENV !== "production") {
spyReport({
object: this.scope,
type: "compute",
name: this.name
});
}
var oldValue = this.value;
var wasSuspended =
/* see #1208 */ this.dependenciesState === IDerivationState.NOT_TRACKING;
var newValue = this.computeValue(true);
var changed = wasSuspended ||
isCaughtException(oldValue) ||
isCaughtException(newValue) ||
!this.equals(oldValue, newValue);
if (changed) {
this.value = newValue;
}
return changed;
};
ComputedValue.prototype.computeValue = function (track) {
this.isComputing = true;
globalState.computationDepth++;
var res;
if (track) {
res = trackDerivedFunction(this, this.derivation, this.scope);
}
else {
if (globalState.disableErrorBoundaries === true) {
res = this.derivation.call(this.scope);
}
else {
try {
res = this.derivation.call(this.scope);
}
catch (e) {
res = new CaughtException(e);
}
}
}
globalState.computationDepth--;
this.isComputing = false;
return res;
};
ComputedValue.prototype.suspend = function () {
if (!this.keepAlive) {
clearObserving(this);
this.value = undefined; // don't hold on to computed value!
}
};
ComputedValue.prototype.observe = function (listener, fireImmediately) {
var _this = this;
var firstTime = true;
var prevValue = undefined;
return autorun(function () {
var newValue = _this.get();
if (!firstTime || fireImmediately) {
var prevU = untrackedStart();
listener({
type: "update",
object: _this,
newValue: newValue,
oldValue: prevValue
});
untrackedEnd(prevU);
}
firstTime = false;
prevValue = newValue;
});
};
ComputedValue.prototype.warnAboutUntrackedRead = function () {
if (process.env.NODE_ENV === "production")
return;
if (this.requiresReaction === true) {
fail("[mobx] Computed value " + this.name + " is read outside a reactive context");
}
if (this.isTracing !== TraceMode.NONE) {
console.log("[mobx.trace] '" + this.name + "' is being read outside a reactive context. Doing a full recompute");
}
if (globalState.computedRequiresReaction) {
console.warn("[mobx] Computed value " + this.name + " is being read outside a reactive context. Doing a full recompute");
}
};
ComputedValue.prototype.toJSON = function () {
return this.get();
};
ComputedValue.prototype.toString = function () {
return this.name + "[" + this.derivation.toString() + "]";
};
ComputedValue.prototype.valueOf = function () {
return toPrimitive(this.get());
};
ComputedValue.prototype[Symbol.toPrimitive] = function () {
return this.valueOf();
};
return ComputedValue;
}());
var isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue);
var IDerivationState;
(function (IDerivationState) {
// before being run or (outside batch and not being observed)
// at this point derivation is not holding any data about dependency tree
IDerivationState[IDerivationState["NOT_TRACKING"] = -1] = "NOT_TRACKING";
// no shallow dependency changed since last computation
// won't recalculate derivation
// this is what makes mobx fast
IDerivationState[IDerivationState["UP_TO_DATE"] = 0] = "UP_TO_DATE";
// some deep dependency changed, but don't know if shallow dependency changed
// will require to check first if UP_TO_DATE or POSSIBLY_STALE
// currently only ComputedValue will propagate POSSIBLY_STALE
//
// having this state is second big optimization:
// don't have to recompute on every dependency change, but only when it's needed
IDerivationState[IDerivationState["POSSIBLY_STALE"] = 1] = "POSSIBLY_STALE";
// A shallow dependency has changed since last computation and the derivation
// will need to recompute when it's needed next.
IDerivationState[IDerivationState["STALE"] = 2] = "STALE";
})(IDerivationState || (IDerivationState = {}));
var TraceMode;
(function (TraceMode) {
TraceMode[TraceMode["NONE"] = 0] = "NONE";
TraceMode[TraceMode["LOG"] = 1] = "LOG";
TraceMode[TraceMode["BREAK"] = 2] = "BREAK";
})(TraceMode || (TraceMode = {}));
var CaughtException = /** @class */ (function () {
function CaughtException(cause) {
this.cause = cause;
// Empty
}
return CaughtException;
}());
function isCaughtException(e) {
return e instanceof CaughtException;
}
/**
* Finds out whether any dependency of the derivation has actually changed.
* If dependenciesState is 1 then it will recalculate dependencies,
* if any dependency changed it will propagate it by changing dependenciesState to 2.
*
* By iterating over the dependencies in the same order that they were reported and
* stopping on the first change, all the recalculations are only called for ComputedValues
* that will be tracked by derivation. That is because we assume that if the first x
* dependencies of the derivation doesn't change then the derivation should run the same way
* up until accessing x-th dependency.
*/
function shouldCompute(derivation) {
switch (derivation.dependenciesState) {
case IDerivationState.UP_TO_DATE:
return false;
case IDerivationState.NOT_TRACKING:
case IDerivationState.STALE:
return true;
case IDerivationState.POSSIBLY_STALE: {
var prevUntracked = untrackedStart(); // no need for those computeds to be reported, they will be picked up in trackDerivedFunction.
var obs = derivation.observing, l = obs.length;
for (var i = 0; i < l; i++) {
var obj = obs[i];
if (isComputedValue(obj)) {
if (globalState.disableErrorBoundaries) {
obj.get();
}
else {
try {
obj.get();
}
catch (e) {
// we are not interested in the value *or* exception at this moment, but if there is one, notify all
untrackedEnd(prevUntracked);
return true;
}
}
// if ComputedValue `obj` actually changed it will be computed and propagated to its observers.
// and `derivation` is an observer of `obj`
// invariantShouldCompute(derivation)
if (derivation.dependenciesState === IDerivationState.STALE) {
untrackedEnd(prevUntracked);
return true;
}
}
}
changeDependenciesStateTo0(derivation);
untrackedEnd(prevUntracked);
return false;
}
}
}
// function invariantShouldCompute(derivation: IDerivation) {
// const newDepState = (derivation as any).dependenciesState
// if (
// process.env.NODE_ENV === "production" &&
// (newDepState === IDerivationState.POSSIBLY_STALE ||
// newDepState === IDerivationState.NOT_TRACKING)
// )
// fail("Illegal dependency state")
// }
function isComputingDerivation() {
return globalState.trackingDerivation !== null; // filter out actions inside computations
}
function checkIfStateModificationsAreAllowed(atom) {
var hasObservers = atom.observers.size > 0;
// Should never be possible to change an observed observable from inside computed, see #798
if (globalState.computationDepth > 0 && hasObservers)
fail(process.env.NODE_ENV !== "production" &&
"Computed values are not allowed to cause side effects by changing observables that are already being observed. Tried to modify: " + atom.name);
// Should not be possible to change observed state outside strict mode, except during initialization, see #563
if (!globalState.allowStateChanges && (hasObservers || globalState.enforceActions === "strict"))
fail(process.env.NODE_ENV !== "production" &&
(globalState.enforceActions
? "Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: "
: "Side effects like changing state are not allowed at this point. Are you trying to modify state from, for example, the render function of a React component? Tried to modify: ") +
atom.name);
}
/**
* Executes the provided function `f` and tracks which observables are being accessed.
* The tracking information is stored on the `derivation` object and the derivation is registered
* as observer of any of the accessed observables.
*/
function trackDerivedFunction(derivation, f, context) {
// pre allocate array allocation + room for variation in deps
// array will be trimmed by bindDependencies
changeDependenciesStateTo0(derivation);
derivation.newObserving = new Array(derivation.observing.length + 100);
derivation.unboundDepsCount = 0;
derivation.runId = ++globalState.runId;
var prevTracking = globalState.trackingDerivation;
globalState.trackingDerivation = derivation;
var result;
if (globalState.disableErrorBoundaries === true) {
result = f.call(context);
}
else {
try {
result = f.call(context);
}
catch (e) {
result = new CaughtException(e);
}
}
globalState.trackingDerivation = prevTracking;
bindDependencies(derivation);
return result;
}
/**
* diffs newObserving with observing.
* update observing to be newObserving with unique observables
* notify observers that become observed/unobserved
*/
function bindDependencies(derivation) {
// invariant(derivation.dependenciesState !== IDerivationState.NOT_TRACKING, "INTERNAL ERROR bindDependencies expects derivation.dependenciesState !== -1");
var prevObserving = derivation.observing;
var observing = (derivation.observing = derivation.newObserving);
var lowestNewObservingDerivationState = IDerivationState.UP_TO_DATE;
// Go through all new observables and check diffValue: (this list can contain duplicates):
// 0: first occurrence, change to 1 and keep it
// 1: extra occurrence, drop it
var i0 = 0, l = derivation.unboundDepsCount;
for (var i = 0; i < l; i++) {
var dep = observing[i];
if (dep.diffValue === 0) {
dep.diffValue = 1;
if (i0 !== i)
observing[i0] = dep;
i0++;
}
// Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
// not hitting the condition
if (dep.dependenciesState > lowestNewObservingDerivationState) {
lowestNewObservingDerivationState = dep.dependenciesState;
}
}
observing.length = i0;
derivation.newObserving = null; // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614)
// Go through all old observables and check diffValue: (it is unique after last bindDependencies)
// 0: it's not in new observables, unobserve it
// 1: it keeps being observed, don't want to notify it. change to 0
l = prevObserving.length;
while (l--) {
var dep = prevObserving[l];
if (dep.diffValue === 0) {
removeObserver(dep, derivation);
}
dep.diffValue = 0;
}
// Go through all new observables and check diffValue: (now it should be unique)
// 0: it was set to 0 in last loop. don't need to do anything.
// 1: it wasn't observed, let's observe it. set back to 0
while (i0--) {
var dep = observing[i0];
if (dep.diffValue === 1) {
dep.diffValue = 0;
addObserver(dep, derivation);
}
}
// Some new observed derivations may become stale during this derivation computation
// so they have had no chance to propagate staleness (#916)
if (lowestNewObservingDerivationState !== IDerivationState.UP_TO_DATE) {
derivation.dependenciesState = lowestNewObservingDerivationState;
derivation.onBecomeStale();
}
}
function clearObserving(derivation) {
// invariant(globalState.inBatch > 0, "INTERNAL ERROR clearObserving should be called only inside batch");
var obs = derivation.observing;
derivation.observing = [];
var i = obs.length;
while (i--)
removeObserver(obs[i], derivation);
derivation.dependenciesState = IDerivationState.NOT_TRACKING;
}
function untracked(action) {
var prev = untrackedStart();
try {
return action();
}
finally {
untrackedEnd(prev);
}
}
function untrackedStart() {
var prev = globalState.trackingDerivation;
globalState.trackingDerivation = null;
return prev;
}
function untrackedEnd(prev) {
globalState.trackingDerivation = prev;
}
/**
* needed to keep `lowestObserverState` correct. when changing from (2 or 1) to 0
*
*/
function changeDependenciesStateTo0(derivation) {
if (derivation.dependenciesState === IDerivationState.UP_TO_DATE)
return;
derivation.dependenciesState = IDerivationState.UP_TO_DATE;
var obs = derivation.observing;
var i = obs.length;
while (i--)
obs[i].lowestObserverState = IDerivationState.UP_TO_DATE;
}
/**
* These values will persist if global state is reset
*/
var persistentKeys = [
"mobxGuid",
"spyListeners",
"enforceActions",
"computedRequiresReaction",
"disableErrorBoundaries",
"runId",
"UNCHANGED"
];
var MobXGlob